]> git.evergreen-ils.org Git - Evergreen.git/blob - Open-ILS/src/perlmods/OpenILS/Application/Circ/Circulate.pm
non-cataloged circs are now working
[Evergreen.git] / Open-ILS / src / perlmods / OpenILS / Application / Circ / Circulate.pm
1 package OpenILS::Application::Circ::Circulate;
2 use base 'OpenSRF::Application';
3 use strict; use warnings;
4 use OpenSRF::EX qw(:try);
5 use Data::Dumper;
6 use OpenSRF::Utils::Logger qw(:logger);
7 use OpenILS::Utils::ScriptRunner;
8 use OpenILS::Application::AppUtils;
9 use OpenILS::Application::Circ::Holds;
10 $Data::Dumper::Indent = 0;
11 my $apputils = "OpenILS::Application::AppUtils";
12 my $holdcode = "OpenILS::Application::Circ::Holds";
13
14 my %scripts;                    # - circulation script filenames
15
16 my $script_libs;                # - any additional script libraries
17 my %cache;
18
19 my %contexts;                   # - Script runner contexts
20
21 # ------------------------------------------------------------------------------
22 # Load the circ script from the config
23 # ------------------------------------------------------------------------------
24 sub initialize {
25
26         my $self = shift;
27         my $conf = OpenSRF::Utils::SettingsClient->new;
28         my @pfx2 = ( "apps", "open-ils.circ","app_settings" );
29         my @pfx = ( @pfx2, "scripts" );
30
31         my $p           = $conf->config_value(  @pfx, 'circ_permit_patron' );
32         my $c           = $conf->config_value(  @pfx, 'circ_permit_copy' );
33         my $d           = $conf->config_value(  @pfx, 'circ_duration' );
34         my $f           = $conf->config_value(  @pfx, 'circ_recurring_fines' );
35         my $m           = $conf->config_value(  @pfx, 'circ_max_fines' );
36         my $pr  = $conf->config_value(  @pfx, 'renew_permit' );
37         my $ph  = $conf->config_value(  @pfx, 'hold_permit' );
38         my $lb  = $conf->config_value(  @pfx2, 'script_path' );
39
40         $logger->error( "Missing circ script(s)" ) 
41                 unless( $p and $c and $d and $f and $m and $pr and $ph );
42
43         $scripts{circ_permit_patron}    = $p;
44         $scripts{circ_permit_copy}              = $c;
45         $scripts{circ_duration}                 = $d;
46         $scripts{circ_recurring_fines}= $f;
47         $scripts{circ_max_fines}                = $m;
48         $scripts{circ_renew_permit}     = $pr;
49         $scripts{hold_permit}                   = $ph;
50
51         $lb = [ $lb ] unless ref($lb);
52         $script_libs = $lb;
53
54         $logger->debug("Loaded rules scripts for circ: " .
55                 "circ permit patron: $p, circ permit copy: $c, ".
56                 "circ duration :$d , circ recurring fines : $f, " .
57                 "circ max fines : $m, circ renew permit : $pr, permit hold: $ph");
58 }
59
60
61 # ------------------------------------------------------------------------------
62 # Loads the necessary circ objects and pushes them into the script environment
63 # Returns ( $data, $evt ).  if $evt is defined, then an
64 # unexpedted event occurred and should be dealt with / returned to the caller
65 # ------------------------------------------------------------------------------
66 sub create_circ_ctx {
67         my %params = @_;
68
69         my $evt;
70         my $ctx = {};
71
72         $ctx->{type}                    = $params{type};
73         $ctx->{renew}                   = $params{renew};
74         $ctx->{noncat}                  = $params{noncat};
75         $ctx->{noncat_type}     = $params{noncat_type};
76
77         $evt = _ctx_add_patron_objects($ctx, %params);
78         return $evt if $evt;
79
80         if( ($params{copy} or $params{copyid} or $params{barcode}) and !$params{noncat} ) {
81                 $evt = _ctx_add_copy_objects($ctx, %params);
82                 return $evt if $evt;
83         }
84
85         _doctor_patron_object($ctx) if $ctx->{patron};
86         _doctor_copy_object($ctx) if $ctx->{copy};
87         _doctor_circ_objects($ctx);
88         _build_circ_script_runner($ctx);
89         _add_script_runner_methods( $ctx );
90
91         return $ctx;
92 }
93
94 sub _ctx_add_patron_objects {
95         my( $ctx, %params) = @_;
96
97         $ctx->{patron}  = $params{patron};
98
99         if(!defined($cache{patron_standings})) {
100                 $cache{patron_standings} = $apputils->fetch_patron_standings();
101                 $cache{group_tree} = $apputils->fetch_permission_group_tree();
102         }
103
104         $ctx->{patron_standings} = $cache{patron_standings};
105         $ctx->{group_tree} = $cache{group_tree};
106
107         $ctx->{patron_circ_summary} = 
108                 $apputils->fetch_patron_circ_summary($ctx->{patron}->id) 
109                 if $params{fetch_patron_circsummary};
110
111         return undef;
112 }
113
114
115 sub _ctx_add_copy_objects {
116         my($ctx, %params)  = @_;
117         my $evt;
118
119         $cache{copy_statuses} = $apputils->fetch_copy_statuses 
120                 if( $params{fetch_copy_statuses} and !defined($cache{copy_statuses}) );
121
122         $cache{copy_locations} = $apputils->fetch_copy_locations 
123                 if( $params{fetch_copy_locations} and !defined($cache{copy_locations}));
124
125         $ctx->{copy_statuses} = $cache{copy_statuses};
126         $ctx->{copy_locations} = $cache{copy_locations};
127
128         my $copy = $params{copy} if $params{copy};
129
130         if(!$copy) {
131
132                 ( $copy, $evt ) = 
133                         $apputils->fetch_copy($params{copyid}) if $params{copyid};
134                 return $evt if $evt;
135
136                 if(!$copy) {
137                         ( $copy, $evt ) = 
138                                 $apputils->fetch_copy_by_barcode( $params{barcode} ) if $params{barcode};
139                         return $evt if $evt;
140                 }
141         }
142
143         $ctx->{copy} = $copy;
144
145         ( $ctx->{title}, $evt ) = $apputils->fetch_record_by_copy( $ctx->{copy}->id );
146         return $evt if $evt;
147
148         return undef;
149 }
150
151
152 # ------------------------------------------------------------------------------
153 # Fleshes parts of the patron object
154 # ------------------------------------------------------------------------------
155 sub _doctor_copy_object {
156
157         my $ctx = shift;
158         my $copy = $ctx->{copy};
159
160         # set the copy status to a status name
161         $copy->status( _get_copy_status( 
162                 $copy, $ctx->{copy_statuses} ) ) if $copy;
163
164         # set the copy location to the location object
165         $copy->location( _get_copy_location( 
166                 $copy, $ctx->{copy_locations} ) ) if $copy;
167
168 }
169
170
171 # ------------------------------------------------------------------------------
172 # Fleshes parts of the copy object
173 # ------------------------------------------------------------------------------
174 sub _doctor_patron_object {
175         my $ctx = shift;
176         my $patron = $ctx->{patron};
177
178         # push the standing object into the patron
179         if(ref($ctx->{patron_standings})) {
180                 for my $s (@{$ctx->{patron_standings}}) {
181                         $patron->standing($s) if ( $s->id eq $ctx->{patron}->standing );
182                 }
183         }
184
185         # set the patron ptofile to the profile name
186         $patron->profile( _get_patron_profile( 
187                 $patron, $ctx->{group_tree} ) ) if $ctx->{group_tree};
188
189         # flesh the org unit
190         $patron->home_ou( 
191                 $apputils->fetch_org_unit( $patron->home_ou ) ) if $patron;
192
193 }
194
195 # recurse and find the patron profile name from the tree
196 # another option would be to grab the groups for the patron
197 # and cycle through those until the "profile" group has been found
198 sub _get_patron_profile { 
199         my( $patron, $group_tree ) = @_;
200         return $group_tree if ($group_tree->id eq $patron->profile);
201         return undef unless ($group_tree->children);
202
203         for my $child (@{$group_tree->children}) {
204                 my $ret = _get_patron_profile( $patron, $child );
205                 return $ret if $ret;
206         }
207         return undef;
208 }
209
210 sub _get_copy_status {
211         my( $copy, $cstatus ) = @_;
212         for my $status (@$cstatus) {
213                 return $status if( $status->id eq $copy->status ) 
214         }
215         return undef;
216 }
217
218 sub _get_copy_location {
219         my( $copy, $locations ) = @_;
220         for my $loc (@$locations) {
221                 return $loc if $loc->id eq $copy->location;
222         }
223 }
224
225
226 # ------------------------------------------------------------------------------
227 # Constructs and shoves data into the script environment
228 # ------------------------------------------------------------------------------
229 sub _build_circ_script_runner {
230         my $ctx = shift;
231
232         $logger->debug("Loading script environment for circulation");
233
234         my $runner;
235         if( $runner = $contexts{$ctx->{type}} ) {
236                 $runner->refresh_context;
237         } else {
238                 $runner = OpenILS::Utils::ScriptRunner->new unless $runner;
239                 $contexts{type} = $runner;
240         }
241
242         for(@$script_libs) {
243                 $logger->debug("Loading circ script lib path $_");
244                 $runner->add_path( $_ );
245         }
246
247         $runner->insert( 'environment.patron',          $ctx->{patron}, 1);
248         $runner->insert( 'environment.title',           $ctx->{title}, 1);
249         $runner->insert( 'environment.copy',            $ctx->{copy}, 1);
250
251         # circ script result
252         $runner->insert( 'result', {} );
253         $runner->insert( 'result.event', 'SUCCESS' );
254
255         $runner->insert('environment.isRenewal', 1) if $ctx->{renew};
256         $runner->insert('environment.isNonCat', 1) if $ctx->{noncat};
257         $runner->insert('environment.nonCatType', $ctx->{noncat_type}) if $ctx->{noncat};
258
259         if(ref($ctx->{patron_circ_summary})) {
260                 $runner->insert( 'environment.patronItemsOut', $ctx->{patron_circ_summary}->[0], 1 );
261                 $runner->insert( 'environment.patronFines', $ctx->{patron_circ_summary}->[1], 1 );
262         }
263
264         $ctx->{runner} = $runner;
265         return $runner;
266 }
267
268
269 sub _add_script_runner_methods {
270         my $ctx = shift;
271         my $runner = $ctx->{runner};
272
273         if( $ctx->{copy} ) {
274                 
275                 # allows a script to fetch a hold that is currently targeting the
276                 # copy in question
277                 $runner->insert_method( 'environment.copy', '__OILS_FUNC_fetch_hold', sub {
278                                 my $key = shift;
279                                 my $hold = $holdcode->fetch_related_holds($ctx->{copy}->id);
280                                 $hold = undef unless $hold;
281                                 $runner->insert( $key, $hold, 1 );
282                         }
283                 );
284         }
285 }
286
287 # ------------------------------------------------------------------------------
288
289 __PACKAGE__->register_method(
290         method  => "permit_circ",
291         api_name        => "open-ils.circ.checkout.permit",
292         notes           => q/
293                 Determines if the given checkout can occur
294                 @param authtoken The login session key
295                 @param params A trailing list of named params including 
296                         barcode : The copy barcode, 
297                         patron : The patron the checkout is occurring for, 
298                         renew : true or false - whether or not this is a renewal
299                 @return The event that occurred during the permit check.  
300                         If all is well, the SUCCESS event is returned
301         /);
302
303 sub permit_circ {
304         my( $self, $client, $authtoken, %params ) = @_;
305
306         my ( $requestor, $patron, $ctx, $evt );
307
308         # check permisson of the requestor
309         ( $requestor, $patron, $evt ) = 
310                 $apputils->checkses_requestor( 
311                 $authtoken, $params{patron}, 'VIEW_PERMIT_CHECKOUT' );
312         return $evt if $evt;
313
314         # fetch and build the circulation environment
315         ( $ctx, $evt ) = create_circ_ctx( %params, 
316                 patron                                                  => $patron, 
317                 type                                                            => 'permit',
318                 fetch_patron_circ_summary       => 1,
319                 fetch_copy_statuses                     => 1, 
320                 fetch_copy_locations                    => 1, 
321                 );
322         return $evt if $evt;
323
324         return _run_permit_scripts($ctx);
325 }
326
327
328 # Runs the patron and copy permit scripts
329 # if this is a non-cat circulation, the copy permit script 
330 # is not run
331 sub _run_permit_scripts {
332
333         my $ctx                 = shift;
334         my $runner              = $ctx->{runner};
335         my $patronid    = $ctx->{patron}->id;
336         my $barcode             = ($ctx->{copy}) ? $ctx->{copy}->barcode : undef;
337
338         $runner->load($scripts{circ_permit_patron});
339         $runner->run or throw OpenSRF::EX::ERROR ("Circ Permit Patron Script Died: $@");
340         my $evtname = $runner->retrieve('result.event');
341         $logger->activity("circ_permit_patron for user $patronid returned event: $evtname");
342
343         return OpenILS::Event->new($evtname) 
344                 if ( $ctx->{noncat} or $evtname ne 'SUCCESS' );
345
346         $runner->load($scripts{circ_permit_copy});
347         $runner->run or throw OpenSRF::EX::ERROR ("Circ Permit Copy Script Died: $@");
348         $evtname = $runner->retrieve('result.event');
349         $logger->activity("circ_permit_patron for user $patronid ".
350                 "and copy $barcode returned event: $evtname");
351
352         return OpenILS::Event->new($evtname);
353
354 }
355
356
357 # ------------------------------------------------------------------------------
358
359 __PACKAGE__->register_method(
360         method  => "checkout",
361         api_name        => "open-ils.circ.checkout",
362         notes => q/
363                 Checks out an item
364                 @param authtoken The login session key
365                 @param params A named list of params including:
366                         copy                    The copy object
367                         barcode         If no copy is provided, the copy is retrieved via barcode
368                         copyid          If no copy or barcode is provide, the copy id will be use
369                         patron          The patron's id
370                         noncat          True if this is a circulation for a non-cataloted item
371                         noncat_type     The non-cataloged type id
372                         noncat_circ_lib The location for the noncat circ.  
373                                 Default is the home org of the staff member
374                 @return The SUCCESS event on success, any other event depending on the error
375         /);
376
377 sub checkout {
378         my( $self, $client, $authtoken, %params ) = @_;
379
380         my ( $requestor, $patron, $ctx, $evt );
381
382         # check permisson of the requestor
383         ( $requestor, $patron, $evt ) = 
384                 $apputils->checkses_requestor( 
385                         $authtoken, $params{patron}, 'COPY_CHECKOUT' );
386         return $evt if $evt;
387
388         return _checkout_noncat( $requestor, $patron, %params ) if $params{noncat};
389
390         # fetch and build the circulation environment
391         ( $ctx, $evt ) = create_circ_ctx( %params, 
392                 patron                                                  => $patron, 
393                 type                                                            => 'checkout',
394                 fetch_patron_circ_summary       => 1,
395                 fetch_copy_statuses                     => 1, 
396                 fetch_copy_locations                    => 1, 
397                 );
398         return $evt if $evt;
399
400         return _run_checkout_scripts( $ctx );
401 }
402
403
404 sub _run_checkout_scripts {
405         my $ctx = shift;
406
407         my $runner = $ctx->{runner};
408
409 #       $runner->load($scripts{circ_duration});
410 #       $runner->run or throw OpenSRF::EX::ERROR ("Circ Duration Script Died: $@");
411
412         return OpenILS::Event->new('SUCCESS', 
413                 payload => { copy => $ctx->{copy} } );
414 }
415
416
417
418 sub _checkout_noncat {
419         my ( $requestor, $patron, %params ) = @_;
420         my $circlib = $params{noncat_circ_lib} || $requestor->home_ou;
421         my( $circ, $evt ) = 
422                 OpenILS::Application::Circ::NonCat::create_non_cat_circ(
423                         $requestor->id, $patron->id, $circlib, $params{noncat_type} );
424         return $evt if $evt;
425         return OpenILS::Event->new('SUCCESS');
426 }
427
428
429 # ------------------------------------------------------------------------------
430
431 __PACKAGE__->register_method(
432         method  => "checkin",
433         api_name        => "open-ils.circ.checkin",
434         notes           => <<"  NOTES");
435         PARAMS( authtoken, barcode => bc )
436         Checks in based on barcode
437         Returns an event object whose payload contains the record, circ, and copy
438         If the item needs to be routed, the event is a ROUTE_COPY event
439         with an additional 'route_to' variable set on the event
440         NOTES
441
442 sub checkin {
443         my( $self, $client, $authtoken, %params ) = @_;
444         my $barcode             = $params{barcode};
445 }
446
447 # ------------------------------------------------------------------------------
448
449 __PACKAGE__->register_method(
450         method  => "renew",
451         api_name        => "open-ils.circ.renew_",
452         notes           => <<"  NOTES");
453         PARAMS( authtoken, circ => circ_id );
454         open-ils.circ.renew(login_session, circ_object);
455         Renews the provided circulation.  login_session is the requestor of the
456         renewal and if the logged in user is not the same as circ->usr, then
457         the logged in user must have RENEW_CIRC permissions.
458         NOTES
459
460 sub renew {
461         my( $self, $client, $authtoken, %params ) = @_;
462         my $circ        = $params{circ};
463 }
464
465         
466
467
468 1;