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->{renew} = $params{renew};
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->{renew};
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.checkout.permit",
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( %params,
318 fetch_patron_circ_summary => 1,
319 fetch_copy_statuses => 1,
320 fetch_copy_locations => 1,
324 return _run_permit_scripts($ctx);
328 # Runs the patron and copy permit scripts
329 # if this is a non-cat circulation, the copy permit script
331 sub _run_permit_scripts {
334 my $runner = $ctx->{runner};
335 my $patronid = $ctx->{patron}->id;
336 my $barcode = ($ctx->{copy}) ? $ctx->{copy}->barcode : undef;
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");
343 return OpenILS::Event->new($evtname)
344 if ( $ctx->{noncat} or $evtname ne 'SUCCESS' );
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");
352 return OpenILS::Event->new($evtname);
357 # ------------------------------------------------------------------------------
359 __PACKAGE__->register_method(
360 method => "checkout",
361 api_name => "open-ils.circ.checkout",
364 @param authtoken The login session key
365 @param params A named list of params including:
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
378 my( $self, $client, $authtoken, %params ) = @_;
380 my ( $requestor, $patron, $ctx, $evt );
382 # check permisson of the requestor
383 ( $requestor, $patron, $evt ) =
384 $apputils->checkses_requestor(
385 $authtoken, $params{patron}, 'COPY_CHECKOUT' );
388 return _checkout_noncat( $requestor, $patron, %params ) if $params{noncat};
390 # fetch and build the circulation environment
391 ( $ctx, $evt ) = create_circ_ctx( %params,
394 fetch_patron_circ_summary => 1,
395 fetch_copy_statuses => 1,
396 fetch_copy_locations => 1,
400 return _run_checkout_scripts( $ctx );
404 sub _run_checkout_scripts {
407 my $runner = $ctx->{runner};
409 # $runner->load($scripts{circ_duration});
410 # $runner->run or throw OpenSRF::EX::ERROR ("Circ Duration Script Died: $@");
412 return OpenILS::Event->new('SUCCESS',
413 payload => { copy => $ctx->{copy} } );
418 sub _checkout_noncat {
419 my ( $requestor, $patron, %params ) = @_;
420 my $circlib = $params{noncat_circ_lib} || $requestor->home_ou;
422 OpenILS::Application::Circ::NonCat::create_non_cat_circ(
423 $requestor->id, $patron->id, $circlib, $params{noncat_type} );
425 return OpenILS::Event->new('SUCCESS');
429 # ------------------------------------------------------------------------------
431 __PACKAGE__->register_method(
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
443 my( $self, $client, $authtoken, %params ) = @_;
444 my $barcode = $params{barcode};
447 # ------------------------------------------------------------------------------
449 __PACKAGE__->register_method(
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.
461 my( $self, $client, $authtoken, %params ) = @_;
462 my $circ = $params{circ};