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 @pfx = ( "apps", "open-ils.circ","app_settings", "scripts" );
30 my $p = $conf->config_value( @pfx, 'permission' );
31 my $d = $conf->config_value( @pfx, 'duration' );
32 my $f = $conf->config_value( @pfx, 'recurring_fines' );
33 my $m = $conf->config_value( @pfx, 'max_fines' );
34 my $pr = $conf->config_value( @pfx, 'permit_renew' );
35 my $ph = $conf->config_value( @pfx, 'permit_hold' );
36 my $lb = $conf->config_value( 'apps', 'open-ils.circ', 'app_settings', 'script_path' );
38 $logger->error( "Missing circ script(s)" )
39 unless( $p and $d and $f and $m and $pr and $ph );
41 $scripts{circ_permit} = $p;
42 $scripts{circ_duration} = $d;
43 $scripts{circ_recurring_fines}= $f;
44 $scripts{circ_max_fines} = $m;
45 $scripts{circ_renew_permit} = $pr;
46 $scripts{hold_permit} = $ph;
48 $lb = [ $lb ] unless ref($lb);
51 $logger->debug("Loaded rules scripts for circ: " .
52 "circ permit : $p, circ duration :$d , circ recurring fines : $f, " .
53 "circ max fines : $m, circ renew permit : $pr, permit hold: $ph");
57 # ------------------------------------------------------------------------------
58 # Loads the necessary circ objects and pushes them into the script environment
59 # Returns ( $data, $evt ). if $evt is defined, then an
60 # unexpedted event occurred and should be dealt with / returned to the caller
61 # ------------------------------------------------------------------------------
65 my $barcode = $params{barcode};
66 my $patron = $params{patron};
67 my $fetch_summary = $params{fetch_patron_circ_summary};
68 my $fetch_cstatus = $params{fetch_copy_statuses};
69 my $fetch_clocs = $params{fetch_copy_locations};
73 $ctx->{patron} = $patron;
74 $ctx->{type} = $params{type};
75 $ctx->{isrenew} = $params{isrenew};
77 if(!defined($cache{patron_standings})) {
78 $cache{patron_standings} = $apputils->fetch_patron_standings();
79 $cache{group_tree} = $apputils->fetch_permission_group_tree();
82 $ctx->{patron_standings} = $cache{patron_standings};
83 $ctx->{group_tree} = $cache{group_tree};
85 $cache{copy_statuses} = $apputils->fetch_copy_statuses
86 if( $fetch_cstatus and !defined($cache{copy_statuses}) );
88 $cache{copy_locations} = $apputils->fetch_copy_locations
89 if( $fetch_clocs and !defined($cache{copy_locations}));
91 $ctx->{copy_statuses} = $cache{copy_statuses};
92 $ctx->{copy_locations} = $cache{copy_locations};
94 $ctx->{patron_circ_summary} =
95 $apputils->fetch_patron_circ_summary($patron->id) if $fetch_summary;
97 ( $ctx->{copy}, $evt ) = $apputils->fetch_copy_by_barcode( $barcode );
98 return ( undef, $evt ) if $evt;
100 ( $ctx->{title}, $evt ) = $apputils->fetch_record_by_copy( $ctx->{copy}->id );
101 return ( undef, $evt ) if $evt;
103 _doctor_circ_objects($ctx);
104 _build_circ_script_runner($ctx);
105 _add_script_runner_methods( $ctx );
111 # ------------------------------------------------------------------------------
112 # Patches up circ objects to make them easier to use from within the script
114 # ------------------------------------------------------------------------------
115 sub _doctor_circ_objects {
118 my $patron = $ctx->{patron};
119 my $copy = $ctx->{copy};
121 for my $s (@{$ctx->{patron_standings}}) {
122 $patron->standing($s) if ( $s->id eq $ctx->{patron}->standing );
125 # set the patron ptofile to the profile name
126 $patron->profile( _get_patron_profile( $patron, $ctx->{group_tree} ) );
129 $patron->home_ou( $apputils->fetch_org_unit( $patron->home_ou ) );
131 # set the copy status to a status name
132 $copy->status( _get_copy_status( $copy, $ctx->{copy_statuses} ) );
134 # set the copy location to the location object
135 $copy->location( _get_copy_location( $copy, $ctx->{copy_locations} ) );
139 # recurse and find the patron profile name from the tree
140 # another option would be to grab the groups for the patron
141 # and cycle through those until the "profile" group has been found
142 sub _get_patron_profile {
143 my( $patron, $group_tree ) = @_;
144 return $group_tree if ($group_tree->id eq $patron->profile);
145 return undef unless ($group_tree->children);
147 for my $child (@{$group_tree->children}) {
148 my $ret = _get_patron_profile( $patron, $child );
154 sub _get_copy_status {
155 my( $copy, $cstatus ) = @_;
156 for my $status (@$cstatus) {
157 return $status if( $status->id eq $copy->status )
162 sub _get_copy_location {
163 my( $copy, $locations ) = @_;
164 for my $loc (@$locations) {
165 return $loc if $loc->id eq $copy->location;
170 # ------------------------------------------------------------------------------
171 # Constructs and shoves data into the script environment
172 # ------------------------------------------------------------------------------
173 sub _build_circ_script_runner {
176 $logger->debug("Loading script environment for circulation");
179 if( $runner = $contexts{$ctx->{type}} ) {
180 $runner->refresh_context;
182 $runner = OpenILS::Utils::ScriptRunner->new unless $runner;
183 $contexts{type} = $runner;
187 $logger->debug("Loading circ script lib path $_");
188 $runner->add_path( $_ );
191 $runner->insert( 'environment.patron', $ctx->{patron}, 1);
192 $runner->insert( 'environment.title', $ctx->{title}, 1);
193 $runner->insert( 'environment.copy', $ctx->{copy}, 1);
196 $runner->insert( 'environment.result', {} );
197 $runner->insert( 'environment.result.event', 'SUCCESS' );
199 $runner->insert('environment.isRenewal', 1) if $ctx->{isrenew};
201 if(ref($ctx->{patron_circ_summary})) {
202 $runner->insert( 'environment.patronItemsOut', $ctx->{patron_circ_summary}->[0], 1 );
203 $runner->insert( 'environment.patronFines', $ctx->{patron_circ_summary}->[1], 1 );
206 $ctx->{runner} = $runner;
211 sub _add_script_runner_methods {
213 my $runner = $ctx->{runner};
215 # allows a script to fetch a hold that is currently targeting the
217 $runner->insert_method( 'environment.copy', '__OILS_FUNC_fetch_hold', sub {
219 my $hold = $holdcode->fetch_open_hold_by_current_copy($ctx->{copy}->id);
220 $hold = undef unless $hold;
221 $runner->insert( $key, $hold, 1 );
226 # ------------------------------------------------------------------------------
228 __PACKAGE__->register_method(
229 method => "permit_circ",
230 api_name => "open-ils.circ.permit_checkout_",
232 Determines if the given checkout can occur
233 @param authtoken The login session key
234 @param params A trailing list of named params including
235 barcode : The copy barcode,
236 patron : The patron the checkout is occurring for,
237 renew : true or false - whether or not this is a renewal
238 @return The event that occurred during the permit check.
239 If all is well, the SUCCESS event is returned
243 my( $self, $client, $authtoken, %params ) = @_;
245 my $barcode = $params{barcode};
246 my $patronid = $params{patron};
247 my $isrenew = $params{renew};
248 my ( $requestor, $patron, $ctx, $evt );
251 # check permisson of the requestor
252 ( $requestor, $patron, $evt ) =
253 $apputils->checkses_requestor(
254 $authtoken, $patronid, 'VIEW_PERMIT_CHECKOUT' );
257 $logger->info("Checking circulation permission for staff: " . $requestor->id .
258 ", patron " . $patron->id . ", and barcode $barcode" );
260 # fetch and build the circulation environment
261 ( $ctx, $evt ) = create_circ_ctx(
265 fetch_patron_circ_summary => 1,
266 fetch_copy_statuses => 1,
267 fetch_copy_locations => 1,
268 isrenew => ($isrenew) ? 1 : 0,
273 my $runner = $ctx->{runner};
275 $runner->load($scripts{circ_permit});
276 $runner->run or throw OpenSRF::EX::ERROR ("Circ Permit Script Died: $@");
278 my $evtname = $runner->retrieve('environment.result.event');
279 $logger->activity("Permit Circ for user $patronid and barcode $barcode returned event: $evtname");
280 return OpenILS::Event->new($evtname);
284 # ------------------------------------------------------------------------------
286 __PACKAGE__->register_method(
287 method => "circulate",
288 api_name => "open-ils.circ.checkout.barcode_",
289 notes => <<" NOTES");
290 Checks out an item based on barcode
291 PARAMS( authtoken, barcode => bc, patron => pid )
295 my( $self, $client, $authtoken, %params ) = @_;
296 my $barcode = $params{barcode};
297 my $patronid = $params{patron};
301 # ------------------------------------------------------------------------------
303 __PACKAGE__->register_method(
305 api_name => "open-ils.circ.checkin.barcode_",
306 notes => <<" NOTES");
307 PARAMS( authtoken, barcode => bc )
308 Checks in based on barcode
309 Returns an event object whose payload contains the record, circ, and copy
310 If the item needs to be routed, the event is a ROUTE_COPY event
311 with an additional 'route_to' variable set on the event
315 my( $self, $client, $authtoken, %params ) = @_;
316 my $barcode = $params{barcode};
319 # ------------------------------------------------------------------------------
321 __PACKAGE__->register_method(
323 api_name => "open-ils.circ.renew_",
324 notes => <<" NOTES");
325 PARAMS( authtoken, circ => circ_id );
326 open-ils.circ.renew(login_session, circ_object);
327 Renews the provided circulation. login_session is the requestor of the
328 renewal and if the logged in user is not the same as circ->usr, then
329 the logged in user must have RENEW_CIRC permissions.
333 my( $self, $client, $authtoken, %params ) = @_;
334 my $circ = $params{circ};