]> git.evergreen-ils.org Git - Evergreen.git/blob - Open-ILS/src/perlmods/OpenILS/Application/Circ/ScriptBuilder.pm
3f8d46f3bc36f9013b66debf749f71b3daf165e2
[Evergreen.git] / Open-ILS / src / perlmods / OpenILS / Application / Circ / ScriptBuilder.pm
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";
13 use Data::Dumper;
14
15 my $holdcode = "OpenILS::Application::Circ::Holds";
16
17 my $evt = "environment";
18 my %GROUP_SET;
19 my $GROUP_TREE;
20 my $ORG_TREE;
21 my @ORG_LIST;
22 my @OU_TYPES;
23
24
25 # -----------------------------------------------------------------------
26 # Possible Args:
27 #  copy
28 #  copy_id
29 #  copy_barcode
30 #
31 #  patron
32 #  patron_id
33 #  patron_barcode
34 #
35 #  fetch_patron_circ_info - load info on items out, overdues, and fines.
36 #
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 # -----------------------------------------------------------------------
40 sub build {
41         my( $class, $args ) = @_;
42
43         my $evt;
44         my @evts;
45
46         my $rollback;
47         my $editor = $$args{editor};
48
49         unless($editor) {
50                 $editor = new_editor(xact => 1);
51                 $rollback = 1;
52         }
53
54         $args->{_direct} = {} unless $args->{_direct};
55         #$args->{editor} = $editor;
56         
57         $evt = fetch_bib_data($editor, $args);
58         push(@evts, $evt) if $evt;
59         $evt = fetch_user_data($editor, $args);
60         push(@evts, $evt) if $evt;
61
62         if(@evts) {
63                 my @e;
64                 push( @e, $_->{textcode} ) for @evts;
65                 $logger->info("script_builder: some events occurred: @e");
66                 $logger->debug("script_builder: some events occurred: " . Dumper(\@evts));
67                 $args->{_events} = \@evts;
68         }
69
70         my $r = build_runner($editor, $args);
71         $editor->rollback if $rollback;
72         return $r;
73 }
74
75
76 sub build_runner {
77         my $editor      = shift;
78         my $ctx         = shift;
79
80         my $runner = OpenILS::Utils::ScriptRunner->new;
81
82         my $gt = $GROUP_TREE;
83         $runner->insert( "$evt.groupTree",      $gt, 1);
84
85
86         $runner->insert( "$evt.patron",         $ctx->{patron}, 1);
87         $runner->insert( "$evt.copy",                   $ctx->{copy}, 1);
88         $runner->insert( "$evt.volume",         $ctx->{volume}, 1);
89         $runner->insert( "$evt.title",          $ctx->{title}, 1);
90
91         if( ref $ctx->{requestor} ) {
92                 $runner->insert( "$evt.requestor",      $ctx->{requestor}, 1);
93                 if($ctx->{requestor}->ws_ou) {
94                         $runner->insert( "$evt.location",       
95                                 $editor->retrieve_actor_org_unit($ctx->{requestor}->ws_ou), 1);
96                 }
97         }
98
99         $runner->insert( "$evt.patronItemsOut", $ctx->{patronItemsOut}, 1 );
100         $runner->insert( "$evt.patronOverdueCount", $ctx->{patronOverdue}, 1 );
101         $runner->insert( "$evt.patronFines", $ctx->{patronFines}, 1 );
102
103         $runner->insert("$evt.$_", $ctx->{_direct}->{$_}, 1) for keys %{$ctx->{_direct}};
104
105         insert_org_methods( $editor, $runner );
106         insert_copy_methods( $editor, $ctx, $runner );
107    insert_user_funcs( $editor, $ctx, $runner );
108
109         return $runner;
110 }
111
112 sub fetch_bib_data {
113         my $e = shift;
114         my $ctx = shift;
115
116         my $flesh = { 
117                 flesh => 2, 
118                 flesh_fields => { 
119                         acp => [ 'location', 'status', 'circ_lib', 'age_protect', 'call_number' ],
120                         acn => [ 'record' ]
121                 } 
122         };
123
124         if( $ctx->{copy} ) {
125                 $ctx->{copy_id} = $ctx->{copy}->id 
126                         unless $ctx->{copy_id} or $ctx->{copy_barcode};
127         }
128
129         my $copy;
130
131         if($ctx->{copy_id}) {
132                 $copy = $e->retrieve_asset_copy(
133                         [$ctx->{copy_id}, $flesh ]) or return $e->event;
134
135         } elsif( $ctx->{copy_barcode} ) {
136
137                 $copy = $e->search_asset_copy(
138                         [{barcode => $ctx->{copy_barcode}, deleted => 'f'}, $flesh ])->[0]
139                         or return $e->event;
140         }
141
142         return undef unless $copy;
143
144         my $vol = $copy->call_number;
145         my $rec = $vol->record;
146         $ctx->{copy} = $copy;
147         $ctx->{volume} = $vol;
148         $copy->call_number($vol->id);
149         $ctx->{title} = $rec;
150         $vol->record($rec->id);
151
152         return undef;
153 }
154
155
156
157 sub fetch_user_data {
158         my( $e, $ctx ) = @_;
159
160         my $flesh = {
161                 flesh => 2,
162                 flesh_fields => {
163                         au => [ qw/ profile home_ou card / ],
164                         aou => [ 'ou_type' ],
165                 }
166         };
167
168         if( $ctx->{patron} ) {
169                 $ctx->{patron_id} = $ctx->{patron}->id unless $ctx->{patron_id};
170         }
171
172         my $patron;
173         
174         if( $ctx->{patron_id} ) {
175                 $patron = $e->retrieve_actor_user([$ctx->{patron_id}, $flesh]);
176
177         } elsif( $ctx->{patron_barcode} ) {
178
179                 my $card = $e->search_actor_card( 
180                         { barcode => $ctx->{patron_barcode} } )->[0] or return $e->event;
181
182                 $patron = $e->search_actor_user( 
183                         [{ card => $card->id }, $flesh ]
184                         )->[0] or return $e->event;
185
186         } elsif( $ctx->{fetch_patron_by_circ_copy} ) {
187
188                 if( my $copy = $ctx->{copy} ) {
189                         my $circs = $e->search_action_circulation(
190                                 { target_copy => $copy->id, checkin_time => undef });
191
192                         if( my $circ = $circs->[0] ) {
193                                 $patron = $e->retrieve_actor_user([$circ->usr, $flesh])
194                                         or return $e->event;
195                         }
196                 }
197         }
198
199         return undef unless $ctx->{patron} = $patron;
200
201         flatten_groups($e);
202
203         $ctx->{requestor} = $ctx->{requestor} || $e->requestor;
204
205         if( $ctx->{fetch_patron_circ_info} ) {
206                 my $circ_counts = 
207                         OpenILS::Application::Actor::_checked_out(1, $e, $patron->id);
208
209                 $ctx->{patronOverdue} = $circ_counts->{overdue} || 0;
210                 my $out = $ctx->{patronOverdue} + $circ_counts->{out};
211
212                 $ctx->{patronItemsOut} = $out 
213                         unless( $ctx->{patronItemsOut} and $ctx->{patronItemsOut} > $out );
214
215                 $logger->debug("script_builder: patron overdue count is " . $ctx->{patronOverdue});
216         }
217
218         if( $ctx->{fetch_patron_money_info} ) {
219                 $ctx->{patronFines} = $U->patron_money_owed($patron->id);
220                 $logger->debug("script_builder: patron fines determined to be ".$ctx->{patronFines});
221         }
222
223         unless( $ctx->{ignore_user_status} ) {
224                 return OpenILS::Event->new('PATRON_INACTIVE')
225                         unless $U->is_true($patron->active);
226         
227                 return OpenILS::Event->new('PATRON_CARD_INACTIVE')
228                         unless $U->is_true($patron->card->active);
229         
230                 my $expire = DateTime::Format::ISO8601->new->parse_datetime(
231                         clense_ISO8601($patron->expire_date));
232         
233                 return OpenILS::Event->new('PATRON_ACCOUNT_EXPIRED')
234                         if( CORE::time > $expire->epoch ) ;
235         }
236
237         return undef;
238 }
239
240
241 sub flatten_groups {
242         my $e = shift;
243         my $tree = shift;
244
245         if(!%GROUP_SET) {
246                 $GROUP_TREE = $e->search_permission_grp_tree(
247                         [
248                                 { parent => undef }, 
249                                 { 
250                                 flesh => 100,
251                                         flesh_fields => { pgt => ['children'] }
252                                 } 
253                         ]
254                 )->[0];
255                 $tree = $GROUP_TREE;
256         }
257
258         return undef unless $tree;
259         $GROUP_SET{$tree->id} = $tree;
260         if( $tree->children ) {
261                 flatten_groups($e, $_) for @{$tree->children};
262         }
263 }
264
265 sub flatten_org_tree {
266         my $tree = shift;
267         return undef unless $tree;
268         push( @ORG_LIST, $tree );
269         if( $tree->children ) {
270                 flatten_org_tree($_) for @{$tree->children};
271         }
272 }
273
274
275
276 sub insert_org_methods {
277         my ( $editor, $runner ) = @_;
278
279         if(!$ORG_TREE) {
280                 $ORG_TREE = $editor->search_actor_org_unit(
281                         [
282                                 {"parent_ou" => undef },
283                                 {
284                                         flesh                           => 2,
285                                         flesh_fields    => { aou =>  ['children'] },
286                                         order_by                        => { aou => 'name'}
287                                 }
288                         ]
289                 )->[0];
290                 flatten_org_tree($ORG_TREE);
291         }
292
293         my $r = $runner;
294         weaken($r);
295
296         $r->insert(__OILS_FUNC_isOrgDescendent  => 
297                 sub {
298                         my( $write_key, $sname, $id ) = @_;
299                         my ($parent)    = grep { $_->shortname eq $sname } @ORG_LIST;
300                         my ($child)             = grep { $_->id == $id } @ORG_LIST;
301                         my $val = is_org_descendent( $parent, $child );
302                         $logger->debug("script_builder: is_org_desc $sname:$id returned val $val, writing to $write_key");
303                         $r->insert($write_key, $val, 1) if $val;
304                         return $val;
305                 }
306         );
307
308         $r->insert(__OILS_FUNC_hasCommonAncestor  => 
309                 sub {
310                         my( $write_key, $orgid1, $orgid2, $depth ) = @_;
311                         my $val = has_common_ancestor( $orgid1, $orgid2, $depth );
312                         $logger->debug("script_builder: has_common_ancestor resturned $val");
313                         $r->insert($write_key, $val, 1) if $val;
314                         return $val;
315                 }
316         );
317 }
318
319
320 sub is_org_descendent {
321         my( $parent, $child ) = @_;
322         return 0 unless $parent and $child;
323         $logger->debug("script_builder: is_org_desc checking parent=".$parent->id.", child=".$child->id);
324         do {
325                 return 0 unless defined $child->parent_ou;
326                 return 1 if $parent->id == $child->id;
327         } while( ($child) = grep { $_->id == $child->parent_ou } @ORG_LIST );
328         return 0;
329 }
330
331 sub has_common_ancestor {
332         my( $org1, $org2, $depth ) = @_;
333         return 0 unless $org1 and $org2;
334         $logger->debug("script_builder: has_common_ancestor checking orgs $org1 : $org2");
335
336         return 1 if $org1 == $org2;
337         ($org1) = grep { $_->id == $org1 } @ORG_LIST;
338         ($org2) = grep { $_->id == $org2 } @ORG_LIST;
339
340         my $p1 = find_parent_at_depth($org1, $depth);
341         my $p2 = find_parent_at_depth($org2, $depth);
342
343         return 1 if $p1->id == $p2->id;
344         return 0;
345 }
346
347
348 sub find_parent_at_depth {
349         my $org = shift;
350         my $depth = shift;
351         return undef unless $org and $depth;
352         fetch_ou_types();
353         do {
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 );
357         return undef;   
358 }
359
360
361 sub fetch_ou_types {
362         return if @OU_TYPES;
363         @OU_TYPES = @{new_editor()->retrieve_all_actor_org_unit_type()};
364 }
365
366 sub insert_copy_methods {
367         my( $e, $ctx,  $runner ) = @_;
368         my $reqr = $ctx->{requestor} || $e->requestor;
369         if( my $copy = $ctx->{copy} ) {
370                 $runner->insert_method( 'environment.copy', '__OILS_FUNC_fetch_best_hold', sub {
371                                 my $key = shift;
372                                 $logger->debug("script_builder: searching for permitted hold for copy ".$copy->barcode);
373                                 my ($hold) = $holdcode->find_nearest_permitted_hold( $e, $copy, $reqr, 1 );  # do we need a new editor here since the xact may be dead??
374                                 $runner->insert( $key, $hold, 1 );
375                         }
376                 );
377         }
378 }
379
380 sub insert_user_funcs {
381    my( $e, $ctx, $runner ) = @_;
382
383    # tells how many holds a user has
384         $runner->insert(__OILS_FUNC_userHoldCount  => 
385                 sub {
386                         my( $write_key, $userid ) = @_;
387          my $val = $holdcode->__user_hold_count(new_editor(), $userid);
388          $logger->info("script_runner: user hold count is $val");
389                         $runner->insert($write_key, $val, 1) if $val;
390                         return $val;
391                 }
392         );
393
394         $runner->insert(__OILS_FUNC_userCircsByCircmod  => 
395                 sub {
396                         my( $write_key, $userid ) = @_;
397
398             # this bug ugly thing generates a count of checkouts by circ_modifier
399              my $query = {
400                 "select" => {
401                     "acp" => ["circ_modifier"]
402                     "circ"=>[{
403                         "aggregate"=>1,
404                         "transform"=>"count",
405                         "alias"=>"count",
406                         "column"=>"id"
407                     }],
408                 },
409                 "from"=>{"acp"=>{"circ"=>{"field"=>"target_copy","fkey"=>"id"}}},
410                 "where"=>{
411                     "+circ"=>{
412                         "checkin_time"=>undef,
413                         "usr"=>$userid,
414                         "-or"=>[
415                             {"stop_fines"=>["LOST","LONGOVERDUE","CLAIMSRETURNED"]},
416                             {"stop_fines"=>undef}
417                         ]
418                     }
419                 }
420             };
421
422             my $mods = $e->json_query($query);
423             my $breakdown = {};
424             $breakdown->{$_->{circ_modifier}} = $_->{count} for @$mods;
425             use OpenSRF::Utils::JSON;
426             $logger->info("script_runner: Loaded checkouts by circ_modifier breakdown:". 
427                 OpenSRF::Utils::JSON->perl2JSON($breakdown));
428                         $runner->insert($write_key, $breakdown, 1) if (keys %$breakdown);
429                 }
430         );
431
432 }
433
434
435
436
437 1;