]> git.evergreen-ils.org Git - working/Evergreen.git/blob - Open-ILS/src/perlmods/OpenILS/Application/Circ/ScriptBuilder.pm
re-factored object fleshing to require fewer network calls
[working/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
108         return $runner;
109 }
110
111 sub fetch_bib_data {
112         my $e = shift;
113         my $ctx = shift;
114
115         my $flesh = { 
116                 flesh => 2, 
117                 flesh_fields => { 
118                         acp => [ 'location', 'status', 'circ_lib', 'age_protect', 'call_number' ],
119                         acn => [ 'record' ]
120                 } 
121         };
122
123         if( $ctx->{copy} ) {
124                 $ctx->{copy_id} = $ctx->{copy}->id 
125                         unless $ctx->{copy_id} or $ctx->{copy_barcode};
126         }
127
128         my $copy;
129
130         if($ctx->{copy_id}) {
131                 $copy = $e->retrieve_asset_copy(
132                         [$ctx->{copy_id}, $flesh ]) or return $e->event;
133
134         } elsif( $ctx->{copy_barcode} ) {
135
136                 $copy = $e->search_asset_copy(
137                         [{barcode => $ctx->{copy_barcode}, deleted => 'f'}, $flesh ])->[0]
138                         or return $e->event;
139         }
140
141         return undef unless $copy;
142
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);
150
151         return undef;
152 }
153
154
155
156 sub fetch_user_data {
157         my( $e, $ctx ) = @_;
158
159         my $flesh = {
160                 flesh => 2,
161                 flesh_fields => {
162                         au => [ qw/ profile home_ou card / ],
163                         aou => [ 'ou_type' ],
164                 }
165         };
166
167         if( $ctx->{patron} ) {
168                 $ctx->{patron_id} = $ctx->{patron}->id unless $ctx->{patron_id};
169         }
170
171         my $patron;
172         
173         if( $ctx->{patron_id} ) {
174                 $patron = $e->retrieve_actor_user([$ctx->{patron_id}, $flesh]);
175
176         } elsif( $ctx->{patron_barcode} ) {
177
178                 my $card = $e->search_actor_card( 
179                         { barcode => $ctx->{patron_barcode} } )->[0] or return $e->event;
180
181                 $patron = $e->search_actor_user( 
182                         [{ card => $card->id }, $flesh ]
183                         )->[0] or return $e->event;
184
185         } elsif( $ctx->{fetch_patron_by_circ_copy} ) {
186
187                 if( my $copy = $ctx->{copy} ) {
188                         my $circs = $e->search_action_circulation(
189                                 { target_copy => $copy->id, checkin_time => undef });
190
191                         if( my $circ = $circs->[0] ) {
192                                 $patron = $e->retrieve_actor_user([$circ->usr, $flesh])
193                                         or return $e->event;
194                         }
195                 }
196         }
197
198         return undef unless $ctx->{patron} = $patron;
199
200         flatten_groups($e);
201
202         $ctx->{requestor} = $ctx->{requestor} || $e->requestor;
203
204         if( $ctx->{fetch_patron_circ_info} ) {
205                 my $circ_counts = 
206                         OpenILS::Application::Actor::_checked_out(1, $e, $patron->id);
207
208                 $ctx->{patronOverdue} = $circ_counts->{overdue} || 0;
209                 my $out = $ctx->{patronOverdue} + $circ_counts->{out};
210
211                 $ctx->{patronItemsOut} = $out 
212                         unless( $ctx->{patronItemsOut} and $ctx->{patronItemsOut} > $out );
213
214                 $logger->debug("script_builder: patron overdue count is " . $ctx->{patronOverdue});
215         }
216
217         if( $ctx->{fetch_patron_money_info} ) {
218                 $ctx->{patronFines} = $U->patron_money_owed($patron->id);
219                 $logger->debug("script_builder: patron fines determined to be ".$ctx->{patronFines});
220         }
221
222         unless( $ctx->{ignore_user_status} ) {
223                 return OpenILS::Event->new('PATRON_INACTIVE')
224                         unless $U->is_true($patron->active);
225         
226                 return OpenILS::Event->new('PATRON_CARD_INACTIVE')
227                         unless $U->is_true($patron->card->active);
228         
229                 my $expire = DateTime::Format::ISO8601->new->parse_datetime(
230                         clense_ISO8601($patron->expire_date));
231         
232                 return OpenILS::Event->new('PATRON_ACCOUNT_EXPIRED')
233                         if( CORE::time > $expire->epoch ) ;
234         }
235
236         return undef;
237 }
238
239
240 sub flatten_groups {
241         my $e = shift;
242         my $tree = shift;
243
244         if(!%GROUP_SET) {
245                 $GROUP_TREE = $e->search_permission_grp_tree(
246                         [
247                                 { parent => undef }, 
248                                 { 
249                                 flesh => 100,
250                                         flesh_fields => { pgt => ['children'] }
251                                 } 
252                         ]
253                 )->[0];
254                 $tree = $GROUP_TREE;
255         }
256
257         return undef unless $tree;
258         $GROUP_SET{$tree->id} = $tree;
259         if( $tree->children ) {
260                 flatten_groups($e, $_) for @{$tree->children};
261         }
262 }
263
264 sub flatten_org_tree {
265         my $tree = shift;
266         return undef unless $tree;
267         push( @ORG_LIST, $tree );
268         if( $tree->children ) {
269                 flatten_org_tree($_) for @{$tree->children};
270         }
271 }
272
273
274
275 sub insert_org_methods {
276         my ( $editor, $runner ) = @_;
277
278         if(!$ORG_TREE) {
279                 $ORG_TREE = $editor->search_actor_org_unit(
280                         [
281                                 {"parent_ou" => undef },
282                                 {
283                                         flesh                           => 2,
284                                         flesh_fields    => { aou =>  ['children'] },
285                                         order_by                        => { aou => 'name'}
286                                 }
287                         ]
288                 )->[0];
289                 flatten_org_tree($ORG_TREE);
290         }
291
292         my $r = $runner;
293         weaken($r);
294
295         $r->insert(__OILS_FUNC_isOrgDescendent  => 
296                 sub {
297                         my( $write_key, $sname, $id ) = @_;
298                         my ($parent)    = grep { $_->shortname eq $sname } @ORG_LIST;
299                         my ($child)             = grep { $_->id == $id } @ORG_LIST;
300                         my $val = is_org_descendent( $parent, $child );
301                         $logger->debug("script_builder: is_org_desc $sname:$id returned val $val, writing to $write_key");
302                         $r->insert($write_key, $val, 1) if $val;
303                         return $val;
304                 }
305         );
306
307         $r->insert(__OILS_FUNC_hasCommonAncestor  => 
308                 sub {
309                         my( $write_key, $orgid1, $orgid2, $depth ) = @_;
310                         my $val = has_common_ancestor( $orgid1, $orgid2, $depth );
311                         $logger->debug("script_builder: has_common_ancestor resturned $val");
312                         $r->insert($write_key, $val, 1) if $val;
313                         return $val;
314                 }
315         );
316 }
317
318
319 sub is_org_descendent {
320         my( $parent, $child ) = @_;
321         return 0 unless $parent and $child;
322         $logger->debug("script_builder: is_org_desc checking parent=".$parent->id.", child=".$child->id);
323         do {
324                 return 0 unless defined $child->parent_ou;
325                 return 1 if $parent->id == $child->id;
326         } while( ($child) = grep { $_->id == $child->parent_ou } @ORG_LIST );
327         return 0;
328 }
329
330 sub has_common_ancestor {
331         my( $org1, $org2, $depth ) = @_;
332         return 0 unless $org1 and $org2;
333         $logger->debug("script_builder: has_common_ancestor checking orgs $org1 : $org2");
334
335         return 1 if $org1 == $org2;
336         ($org1) = grep { $_->id == $org1 } @ORG_LIST;
337         ($org2) = grep { $_->id == $org2 } @ORG_LIST;
338
339         my $p1 = find_parent_at_depth($org1, $depth);
340         my $p2 = find_parent_at_depth($org2, $depth);
341
342         return 1 if $p1->id == $p2->id;
343         return 0;
344 }
345
346
347 sub find_parent_at_depth {
348         my $org = shift;
349         my $depth = shift;
350         return undef unless $org and $depth;
351         fetch_ou_types();
352         do {
353                 my ($t) = grep { $_->id == $org->ou_type } @OU_TYPES;
354                 return $org if $t->depth == $depth;
355         } while( ($org) = grep { $_->id == $org->parent_ou } @ORG_LIST );
356         return undef;   
357 }
358
359
360 sub fetch_ou_types {
361         return if @OU_TYPES;
362         @OU_TYPES = @{new_editor()->retrieve_all_actor_org_unit_type()};
363 }
364
365 sub insert_copy_methods {
366         my( $e, $ctx,  $runner ) = @_;
367         my $reqr = $ctx->{requestor} || $e->requestor;
368         if( my $copy = $ctx->{copy} ) {
369                 $runner->insert_method( 'environment.copy', '__OILS_FUNC_fetch_best_hold', sub {
370                                 my $key = shift;
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, $reqr );
374                                 $runner->insert( $key, $hold, 1 );
375                         }
376                 );
377         }
378 }
379
380
381
382
383
384 1;