01d53fd4efcae7b790d01097435fa8963504df96
[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 $editor = $$args{editor} || new_editor(xact => 1);
47
48         $args->{_direct} = {} unless $args->{_direct};
49         
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;
54
55         if(@evts) {
56                 my @e;
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;
61         }
62
63         return build_runner($editor, $args);
64 }
65
66
67 sub build_runner {
68         my $editor      = shift;
69         my $ctx         = shift;
70
71         my $runner = OpenILS::Utils::ScriptRunner->new;
72
73         my $gt = $GROUP_TREE;
74         $runner->insert( "$evt.groupTree",      $gt, 1);
75
76
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);
81
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);
87                 }
88         }
89
90         $runner->insert( "$evt.patronItemsOut", $ctx->{patronItemsOut}, 1 );
91         $runner->insert( "$evt.patronOverdueCount", $ctx->{patronOverdue}, 1 );
92         $runner->insert( "$evt.patronFines", $ctx->{patronFines}, 1 );
93
94         $runner->insert("$evt.$_", $ctx->{_direct}->{$_}, 1) for keys %{$ctx->{_direct}};
95
96         insert_org_methods( $editor, $runner );
97         insert_copy_methods( $editor, $ctx, $runner );
98
99         return $runner;
100 }
101
102 sub fetch_bib_data {
103         my $e = shift;
104         my $ctx = shift;
105
106         if(!$ctx->{copy}) {
107
108                 my $flesh = { flesh => 1, flesh_fields => { acp => [ 'location', 'status', 'circ_lib' ] } };
109
110                 if($ctx->{copy_id}) {
111                         $ctx->{copy} = $e->retrieve_asset_copy(
112                                 [$ctx->{copy_id}, $flesh ]) or return $e->event;
113
114                 } elsif( $ctx->{copy_barcode} ) {
115
116                         $ctx->{copy} = $e->search_asset_copy(
117                                 [{barcode => $ctx->{copy_barcode}, deleted => 'f'}, $flesh ])->[0]
118                                 or return $e->event;
119                 }
120         }
121
122         return undef unless my $copy = $ctx->{copy};
123
124         $copy->location($e->retrieve_asset_copy_location($copy->location))
125                 unless( ref $copy->location );
126
127         $copy->status($e->retrieve_config_copy_status($copy->status))
128                 unless( ref $copy->status );
129
130         $copy->circ_lib( 
131                 $e->retrieve_actor_org_unit($copy->circ_lib)) 
132                 unless ref $copy->circ_lib;
133
134         $ctx->{volume} = $e->retrieve_asset_call_number(
135                 $ctx->{copy}->call_number) or return $e->event;
136
137         $ctx->{title} = $e->retrieve_biblio_record_entry(
138                 $ctx->{volume}->record) or return $e->event;
139
140         $copy->age_protect(
141                 $e->retrieve_config_rules_age_hold_protect($copy->age_protect))
142                 if $ctx->{flesh_age_protect} and $copy->age_protect;
143
144         return undef;
145 }
146
147
148
149 sub fetch_user_data {
150         my( $e, $ctx ) = @_;
151         
152         if(!$ctx->{patron}) {
153
154                 if( $ctx->{patron_id} ) {
155                         $ctx->{patron} = $e->retrieve_actor_user($ctx->{patron_id});
156
157                 } elsif( $ctx->{patron_barcode} ) {
158
159                         my $card = $e->search_actor_card( 
160                                 { barcode => $ctx->{patron_barcode} } )->[0] or return $e->event;
161
162                         $ctx->{patron} = $e->search_actor_user( 
163                                 { card => $card->id })->[0] or return $e->event;
164
165                 } elsif( $ctx->{fetch_patron_by_circ_copy} ) {
166
167                         if( my $copy = $ctx->{copy} ) {
168                                 my $circs = $e->search_action_circulation(
169                                         { target_copy => $copy->id, checkin_time => undef });
170
171                                 if( my $circ = $circs->[0] ) {
172                                         $ctx->{patron} = $e->retrieve_actor_user($circ->usr)
173                                                 or return $e->event;
174                                 }
175                         }
176                 }
177         }
178
179         return undef unless my $patron = $ctx->{patron};
180
181         unless( $ctx->{ignore_user_status} ) {
182                 return OpenILS::Event->new('PATRON_INACTIVE')
183                         unless $U->is_true($patron->active);
184         
185                 $patron->card($e->retrieve_actor_card($patron->card))
186                         unless ref $patron->card;
187         
188                 return OpenILS::Event->new('PATRON_CARD_INACTIVE')
189                         unless $U->is_true($patron->card->active);
190         
191                 my $expire = DateTime::Format::ISO8601->new->parse_datetime(
192                         clense_ISO8601($patron->expire_date));
193         
194                 return OpenILS::Event->new('PATRON_ACCOUNT_EXPIRED')
195                         if( CORE::time > $expire->epoch ) ;
196         }
197
198         $patron->home_ou( 
199                 $e->retrieve_actor_org_unit($patron->home_ou) ) 
200                 unless ref $patron->home_ou;
201
202         $patron->home_ou->ou_type(
203                 $patron->home_ou->ou_type->id) 
204                 if ref $patron->home_ou->ou_type;
205
206         if(!%GROUP_SET) {
207                 $GROUP_TREE = $e->search_permission_grp_tree(
208                         [
209                                 { parent => undef }, 
210                                 { 
211                                         flesh => 100,
212                                         flesh_fields => { pgt => ['children'] }
213                                 } 
214                         ]
215                 )->[0];
216
217                 flatten_groups($GROUP_TREE);
218         }
219
220         $patron->profile( $GROUP_SET{$patron->profile} )
221                 unless ref $patron->profile;
222
223
224         $ctx->{requestor} = $ctx->{requestor} || $e->requestor;
225
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) );  
230         #}
231
232         if( $ctx->{fetch_patron_circ_info} ) {
233                 my $circ_counts = 
234                         OpenILS::Application::Actor::_checked_out(1, $e, $patron->id);
235
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});
239         }
240
241         if( $ctx->{fetch_patron_money_info} ) {
242                 # Grab the fines
243 #               my $fxacts = $e->search_money_billable_transaction_summary(
244 #                       { usr => $patron->id, balance_owed => { "!=" => 0 }, xact_finish => undef });
245 #
246 #               my $fines = 0;
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});
251         }
252
253         return undef;
254 }
255
256
257 sub flatten_groups {
258         my $tree = shift;
259         return undef unless $tree;
260         $GROUP_SET{$tree->id} = $tree;
261         if( $tree->children ) {
262                 flatten_groups($_) for @{$tree->children};
263         }
264 }
265
266 sub flatten_org_tree {
267         my $tree = shift;
268         return undef unless $tree;
269         push( @ORG_LIST, $tree );
270         if( $tree->children ) {
271                 flatten_org_tree($_) for @{$tree->children};
272         }
273 }
274
275
276
277 sub insert_org_methods {
278         my ( $editor, $runner ) = @_;
279
280         if(!$ORG_TREE) {
281                 $ORG_TREE = $editor->search_actor_org_unit(
282                         [
283                                 {"parent_ou" => undef },
284                                 {
285                                         flesh                           => 2,
286                                         flesh_fields    => { aou =>  ['children'] },
287                                         order_by                        => { aou => 'name'}
288                                 }
289                         ]
290                 )->[0];
291                 flatten_org_tree($ORG_TREE);
292         }
293
294         my $r = $runner;
295         weaken($r);
296
297         $r->insert(__OILS_FUNC_isOrgDescendent  => 
298                 sub {
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;
305                         return $val;
306                 }
307         );
308
309         $r->insert(__OILS_FUNC_hasCommonAncestor  => 
310                 sub {
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;
315                         return $val;
316                 }
317         );
318 }
319
320
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);
325         do {
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 );
329         return 0;
330 }
331
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");
336
337         return 1 if $org1 == $org2;
338         ($org1) = grep { $_->id == $org1 } @ORG_LIST;
339         ($org2) = grep { $_->id == $org2 } @ORG_LIST;
340
341         my $p1 = find_parent_at_depth($org1, $depth);
342         my $p2 = find_parent_at_depth($org2, $depth);
343
344         return 1 if $p1->id == $p2->id;
345         return 0;
346 }
347
348
349 sub find_parent_at_depth {
350         my $org = shift;
351         my $depth = shift;
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         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, $e->requestor );
374                                 $runner->insert( $key, $hold, 1 );
375                         }
376                 );
377         }
378 }
379
380
381
382
383
384 1;