1 package OpenILS::Application::Circ::Circulate;
2 use base 'OpenSRF::Application';
3 use strict; use warnings;
4 use OpenSRF::EX qw(:try);
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";
14 my %scripts; # - circulation script filenames
16 my $script_libs; # - any additional script libraries
19 my %contexts; # - Script runner contexts
21 # ------------------------------------------------------------------------------
22 # Load the circ script from the config
23 # ------------------------------------------------------------------------------
27 my $conf = OpenSRF::Utils::SettingsClient->new;
28 my @pfx2 = ( "apps", "open-ils.circ","app_settings" );
29 my @pfx = ( @pfx2, "scripts" );
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' );
40 $logger->error( "Missing circ script(s)" )
41 unless( $p and $c and $d and $f and $m and $pr and $ph );
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;
51 $lb = [ $lb ] unless ref($lb);
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");
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 # ------------------------------------------------------------------------------
72 $ctx->{type} = $params{type};
73 $ctx->{isrenew} = $params{isrenew};
74 $ctx->{noncat} = $params{noncat};
75 $ctx->{noncat_type} = $params{noncat_type};
77 $evt = _ctx_add_patron_objects($ctx, %params);
80 if( ($params{copy} or $params{copyid} or $params{barcode}) and !$params{noncat} ) {
81 $evt = _ctx_add_copy_objects($ctx, %params);
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 );
94 sub _ctx_add_patron_objects {
95 my( $ctx, %params) = @_;
97 $ctx->{patron} = $params{patron};
99 if(!defined($cache{patron_standings})) {
100 $cache{patron_standings} = $apputils->fetch_patron_standings();
101 $cache{group_tree} = $apputils->fetch_permission_group_tree();
104 $ctx->{patron_standings} = $cache{patron_standings};
105 $ctx->{group_tree} = $cache{group_tree};
107 $ctx->{patron_circ_summary} =
108 $apputils->fetch_patron_circ_summary($ctx->{patron}->id)
109 if $params{fetch_patron_circsummary};
115 sub _ctx_add_copy_objects {
116 my($ctx, %params) = @_;
119 $cache{copy_statuses} = $apputils->fetch_copy_statuses
120 if( $params{fetch_copy_statuses} and !defined($cache{copy_statuses}) );
122 $cache{copy_locations} = $apputils->fetch_copy_locations
123 if( $params{fetch_copy_locations} and !defined($cache{copy_locations}));
125 $ctx->{copy_statuses} = $cache{copy_statuses};
126 $ctx->{copy_locations} = $cache{copy_locations};
128 my $copy = $params{copy} if $params{copy};
133 $apputils->fetch_copy($params{copyid}) if $params{copyid};
138 $apputils->fetch_copy_by_barcode( $params{barcode} ) if $params{barcode};
143 $ctx->{copy} = $copy;
145 ( $ctx->{title}, $evt ) = $apputils->fetch_record_by_copy( $ctx->{copy}->id );
152 # ------------------------------------------------------------------------------
153 # Fleshes parts of the patron object
154 # ------------------------------------------------------------------------------
155 sub _doctor_copy_object {
158 my $copy = $ctx->{copy};
160 # set the copy status to a status name
161 $copy->status( _get_copy_status(
162 $copy, $ctx->{copy_statuses} ) ) if $copy;
164 # set the copy location to the location object
165 $copy->location( _get_copy_location(
166 $copy, $ctx->{copy_locations} ) ) if $copy;
171 # ------------------------------------------------------------------------------
172 # Fleshes parts of the copy object
173 # ------------------------------------------------------------------------------
174 sub _doctor_patron_object {
176 my $patron = $ctx->{patron};
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 );
185 # set the patron ptofile to the profile name
186 $patron->profile( _get_patron_profile(
187 $patron, $ctx->{group_tree} ) ) if $ctx->{group_tree};
191 $apputils->fetch_org_unit( $patron->home_ou ) ) if $patron;
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);
203 for my $child (@{$group_tree->children}) {
204 my $ret = _get_patron_profile( $patron, $child );
210 sub _get_copy_status {
211 my( $copy, $cstatus ) = @_;
212 for my $status (@$cstatus) {
213 return $status if( $status->id eq $copy->status )
218 sub _get_copy_location {
219 my( $copy, $locations ) = @_;
220 for my $loc (@$locations) {
221 return $loc if $loc->id eq $copy->location;
226 # ------------------------------------------------------------------------------
227 # Constructs and shoves data into the script environment
228 # ------------------------------------------------------------------------------
229 sub _build_circ_script_runner {
232 $logger->debug("Loading script environment for circulation");
235 if( $runner = $contexts{$ctx->{type}} ) {
236 $runner->refresh_context;
238 $runner = OpenILS::Utils::ScriptRunner->new unless $runner;
239 $contexts{type} = $runner;
243 $logger->debug("Loading circ script lib path $_");
244 $runner->add_path( $_ );
247 $runner->insert( 'environment.patron', $ctx->{patron}, 1);
248 $runner->insert( 'environment.title', $ctx->{title}, 1);
249 $runner->insert( 'environment.copy', $ctx->{copy}, 1);
252 $runner->insert( 'result', {} );
253 $runner->insert( 'result.event', 'SUCCESS' );
255 $runner->insert('environment.isRenewal', 1) if $ctx->{isrenew};
256 $runner->insert('environment.isNonCat', 1) if $ctx->{noncat};
257 $runner->insert('environment.nonCatType', $ctx->{noncat_type}) if $ctx->{noncat};
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 );
264 $ctx->{runner} = $runner;
269 sub _add_script_runner_methods {
271 my $runner = $ctx->{runner};
275 # allows a script to fetch a hold that is currently targeting the
277 $runner->insert_method( 'environment.copy', '__OILS_FUNC_fetch_hold', sub {
279 my $hold = $holdcode->fetch_related_holds($ctx->{copy}->id);
280 $hold = undef unless $hold;
281 $runner->insert( $key, $hold, 1 );
287 # ------------------------------------------------------------------------------
289 __PACKAGE__->register_method(
290 method => "permit_circ",
291 api_name => "open-ils.circ.permit_checkout_",
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
304 my( $self, $client, $authtoken, %params ) = @_;
306 my ( $requestor, $patron, $ctx, $evt );
308 # check permisson of the requestor
309 ( $requestor, $patron, $evt ) =
310 $apputils->checkses_requestor(
311 $authtoken, $params{patron}, 'VIEW_PERMIT_CHECKOUT' );
314 # fetch and build the circulation environment
315 ( $ctx, $evt ) = create_circ_ctx(
316 barcode => $params{barcode},
317 copyid => $params{copyid},
318 copy => $params{copy},
321 fetch_patron_circ_summary => 1,
322 fetch_copy_statuses => 1,
323 fetch_copy_locations => 1,
324 isrenew => ($params{renew}) ? 1 : 0,
325 noncat => ($params{noncat}) ? 1 : 0,
326 noncat_type => $params{noncat_type},
330 return _run_permit_scripts($ctx);
334 # Runs the patron and copy permit scripts
335 # if this is a non-cat circulation, the copy permit script
337 sub _run_permit_scripts {
340 my $runner = $ctx->{runner};
341 my $patronid = $ctx->{patron}->id;
342 my $barcode = ($ctx->{copy}) ? $ctx->{copy}->barcode : undef;
344 $runner->load($scripts{circ_permit_patron});
345 $runner->run or throw OpenSRF::EX::ERROR ("Circ Permit Patron Script Died: $@");
346 my $evtname = $runner->retrieve('result.event');
347 $logger->activity("circ_permit_patron for user $patronid returned event: $evtname");
349 return OpenILS::Event->new($evtname)
350 if ( $ctx->{noncat} or $evtname ne 'SUCCESS' );
352 $runner->load($scripts{circ_permit_copy});
353 $runner->run or throw OpenSRF::EX::ERROR ("Circ Permit Copy Script Died: $@");
354 $evtname = $runner->retrieve('result.event');
355 $logger->activity("circ_permit_patron for user $patronid ".
356 "and copy $barcode returned event: $evtname");
358 return OpenILS::Event->new($evtname);
363 # ------------------------------------------------------------------------------
365 __PACKAGE__->register_method(
366 method => "checkout",
367 api_name => "open-ils.circ.checkout",
370 @param authtoken The login session key
371 @param params A named list of params including:
373 barcode If no copy is provided, the copy is retrieved via barcode
374 copyid If no copy or barcode is provide, the copy id will be use
375 patron The patron's id
376 noncat True if this is a circulation for a non-cataloted item
377 noncat_type The non-cataloged type id
378 noncat_circ_lib The location for the noncat circ.
379 Default is the home org of the staff member
380 @return The SUCCESS event on success, any other event depending on the error
384 my( $self, $client, $authtoken, %params ) = @_;
386 my ( $requestor, $patron, $ctx, $evt );
388 # check permisson of the requestor
389 ( $requestor, $patron, $evt ) =
390 $apputils->checkses_requestor(
391 $authtoken, $params{patron}, 'COPY_CHECKOUT' );
394 return _checkout_noncat( $requestor, $patron, %params ) if $params{noncat};
396 # fetch and build the circulation environment
397 ( $ctx, $evt ) = create_circ_ctx(
398 barcode => $params{barcode},
399 copyid => $params{copyid},
400 copy => $params{copy},
403 fetch_patron_circ_summary => 1,
404 fetch_copy_statuses => 1,
405 fetch_copy_locations => 1,
406 isrenew => ($params{renew}) ? 1 : 0,
407 noncat => ($params{noncat}) ? 1 : 0,
411 return _run_checkout_scripts( $ctx );
416 sub _run_checkout_scripts {
419 my $runner = $ctx->{runner};
421 # $runner->load($scripts{circ_duration});
422 # $runner->run or throw OpenSRF::EX::ERROR ("Circ Duration Script Died: $@");
424 return OpenILS::Event->new('SUCCESS',
425 payload => { copy => $ctx->{copy} } );
430 sub _checkout_noncat {
431 my ( $requestor, $patron, %params ) = @_;
432 my $circlib = $params{noncat_circ_lib} || $requestor->home_ou;
434 OpenILS::Application::Circ::NonCat::create_non_cat_circ(
435 $requestor->id, $patron->id, $circlib, $params{noncat_type} );
437 return OpenILS::Event->new('SUCCESS');
441 # ------------------------------------------------------------------------------
443 __PACKAGE__->register_method(
445 api_name => "open-ils.circ.checkin.barcode_",
446 notes => <<" NOTES");
447 PARAMS( authtoken, barcode => bc )
448 Checks in based on barcode
449 Returns an event object whose payload contains the record, circ, and copy
450 If the item needs to be routed, the event is a ROUTE_COPY event
451 with an additional 'route_to' variable set on the event
455 my( $self, $client, $authtoken, %params ) = @_;
456 my $barcode = $params{barcode};
459 # ------------------------------------------------------------------------------
461 __PACKAGE__->register_method(
463 api_name => "open-ils.circ.renew_",
464 notes => <<" NOTES");
465 PARAMS( authtoken, circ => circ_id );
466 open-ils.circ.renew(login_session, circ_object);
467 Renews the provided circulation. login_session is the requestor of the
468 renewal and if the logged in user is not the same as circ->usr, then
469 the logged in user must have RENEW_CIRC permissions.
473 my( $self, $client, $authtoken, %params ) = @_;
474 my $circ = $params{circ};