1 package OpenILS::Application::Circ::Rules;
2 use base qw/OpenSRF::Application/;
3 use strict; use warnings;
5 use OpenSRF::Utils::SettingsClient;
6 use OpenILS::Utils::Fieldmapper;
8 use Template qw(:template);
11 use Time::HiRes qw(time);
12 use OpenILS::Utils::ModsParser;
15 use OpenSRF::EX qw(:try);
17 use OpenILS::Application::AppUtils;
18 my $apputils = "OpenILS::Application::AppUtils";
19 use Digest::MD5 qw(md5_hex);
21 # ----------------------------------------------------------------
24 my $permission_script;
26 my $recurring_fines_script;
28 # ----------------------------------------------------------------
31 # data used for this circulation transaction
32 my $circ_objects = {};
34 # some static data from the database
38 my $shelving_locations;
43 # memcache for caching the circ objects
49 my $conf = OpenSRF::Utils::SettingsClient->new;
51 # ----------------------------------------------------------------
52 # set up the rules scripts
53 # ----------------------------------------------------------------
54 $circ_script = $conf->config_value(
55 "apps", "open-ils.circ","app_settings", "rules", "main");
57 $permission_script = $conf->config_value(
58 "apps", "open-ils.circ","app_settings", "rules", "permission");
60 $duration_script = $conf->config_value(
61 "apps", "open-ils.circ","app_settings", "rules", "duration");
63 $recurring_fines_script = $conf->config_value(
64 "apps", "open-ils.circ","app_settings", "rules", "recurring_fines");
66 $max_fines_script = $conf->config_value(
67 "apps", "open-ils.circ","app_settings", "rules", "max_fines");
70 $cache_handle = OpenSRF::Utils::Cache->new();
75 # ----------------------------------------------------------------
76 # Collect all of the objects necessary for calculating the
78 # ----------------------------------------------------------------
79 sub gather_circ_objects {
80 my( $session, $barcode_string, $patron_id ) = @_;
82 throw OpenSRF::EX::ERROR
83 ("gather_circ_objects needs data")
84 unless ($barcode_string and $patron_id);
86 warn "Gathering circ objects with barcode $barcode_string and patron id $patron_id\n";
90 # see if all of the circ objects are in cache
91 my $cache_key = "circ_object_" . md5_hex( $barcode_string, $patron_id );
92 $circ_objects = $cache_handle->get_cache($cache_key);
95 $stash = Template::Stash->new(
96 circ_objects => $circ_objects,
98 target_copy_status => 1,
103 # grab the patron standing list of we don't already have it
104 # only necessary if the circ objects have not been built yet
105 if(!$patron_standings) {
106 my $standing_req = $session->request(
107 "open-ils.storage.direct.config.standing.retrieve.all.atomic");
108 $patron_standings = $standing_req->gather(1);
110 { map { (''.$_->id => $_->value) } @$patron_standings };
113 # grab patron profiles
114 if(!$patron_profiles) {
115 my $profile_req = $session->request(
116 "open-ils.storage.direct.actor.profile.retrieve.all.atomic");
117 $patron_profiles = $profile_req->gather(1);
119 { map { (''.$_->id => $_->name) } @$patron_profiles };
123 my $copy_req = $session->request(
124 "open-ils.storage.fleshed.asset.copy.search.barcode",
127 my $patron_req = $session->request(
128 "open-ils.storage.direct.actor.user.retrieve",
131 my $copy = $copy_req->gather(1)->[0];
132 $copy->status( $copy->status->name );
133 $circ_objects->{copy} = $copy;
135 my $patron = $patron_req->gather(1);
136 $patron->standing($patron_standings->{$patron->standing()});
137 $patron->profile( $patron_profiles->{$patron->profile});
138 $circ_objects->{patron} = $patron;
141 my $title_req = $session->request(
142 "open-ils.storage.fleshed.biblio.record_entry.retrieve_by_copy",
143 $circ_objects->{copy}->id() );
145 $circ_objects->{title} = $title_req->gather(1);
147 $cache_handle->put_cache( $cache_key, $circ_objects, 30 );
149 $stash = Template::Stash->new(
150 circ_objects => $circ_objects,
152 target_copy_status => 1,
163 my $template = Template->new(
171 my $status = $template->process($circ_script);
174 throw OpenSRF::EX::ERROR
175 ("Error processing circ script " . $template->error());
178 warn "Script result: $result\n";
184 __PACKAGE__->register_method(
185 method => "permit_circ",
186 api_name => "open-ils.circ.permit_checkout",
190 my( $self, $client, $user_session, $barcode, $user_id, $outstanding_count ) = @_;
192 $outstanding_count ||= 0;
194 my $session = OpenSRF::AppSession->create("open-ils.storage");
196 # collect items necessary for circ calculation
197 gather_circ_objects( $session, $barcode, $user_id );
199 $stash->set("run_block", $permission_script);
201 # grab the number of copies checked out by the patron as
202 # well as the total fines
203 my $summary_req = $session->request(
204 "open-ils.storage.action.circulation.patron_summary",
205 $stash->get("circ_objects")->{patron}->id );
206 my $summary = $summary_req->gather(1);
208 $stash->set("patron_copies", $summary->[0] + $outstanding_count );
209 $stash->set("patron_fines", $summary->[1] );
211 # run the permissibility script
213 my $obj = $stash->get("circ_objects");
215 # turn the biblio record into a friendly object
216 my $u = OpenILS::Utils::ModsParser->new();
217 $u->start_mods_batch( $obj->{title}->marc() );
218 my $mods = $u->finish_mods_batch();
220 my $arr = $stash->get("result");
221 return { record => $mods, status => $arr->[0], text => $arr->[1] };
226 __PACKAGE__->register_method(
227 method => "circulate",
228 api_name => "open-ils.circ.checkout.barcode",
232 my( $self, $client, $user_session, $barcode, $patron ) = @_;
235 my $session = $apputils->start_db_session();
237 gather_circ_objects( $session, $barcode, $patron );
239 # grab the copy statuses if we don't already have them
240 if(!$copy_statuses) {
241 my $csreq = $session->request(
242 "open-ils.storage.direct.config.copy_status.retrieve.all.atomic" );
243 $copy_statuses = $csreq->gather(1);
246 # put copy statuses into the stash
247 $stash->set("copy_statuses", $copy_statuses );
249 my $copy = $circ_objects->{copy};
250 my ($circ, $duration, $recurring, $max) = run_circ_scripts($session);
252 # commit new circ object to db
253 my $commit = $session->request(
254 "open-ils.storage.direct.action.circulation.create",
256 my $id = $commit->gather(1);
259 throw OpenSRF::EX::ERROR
260 ("Error creating new circulation object");
263 # update the copy with the new circ
264 $copy->status( $stash->get("target_copy_status") );
265 $copy->location( $copy->location->id );
266 $copy->circ_lib( $copy->circ_lib->id );
269 my $copy_update = $session->request(
270 "open-ils.storage.direct.asset.copy.update",
272 $copy_update->gather(1);
274 $apputils->commit_db_session($session);
276 # remove our circ object from the cache
277 $cache_handle->delete_cache("circ_object_" . md5_hex($barcode, $patron));
279 # re-retrieve the the committed circ object
280 $circ = $apputils->simple_scalar_request(
282 "open-ils.storage.direct.action.circulation.retrieve",
286 # push the rules and due date into the circ object
287 $circ->duration_rule($duration);
288 $circ->max_fine_rule($max);
289 $circ->recuring_fine_rule($recurring);
292 OpenSRF::Utils->interval_to_seconds(
293 $circ->duration ) + int(time());
295 $circ->due_date($due_date);
303 # runs the duration, recurring_fines, and max_fines scripts.
304 # builds the new circ object based on the rules returned from
306 # returns (circ, duration_rule, recurring_fines_rule, max_fines_rule)
307 sub run_circ_scripts {
310 # go through all of the scripts and process
311 # each script returns
312 # [ rule_name, level (appropriate to the script) ]
313 $stash->set("result", [] );
314 $stash->set("run_block", $duration_script);
316 my $duration_rule = $stash->get("result");
318 $stash->set("run_block", $recurring_fines_script);
319 $stash->set("result", [] );
321 my $rec_fines_rule = $stash->get("result");
323 $stash->set("run_block", $max_fines_script);
324 $stash->set("result", [] );
326 my $max_fines_rule = $stash->get("result");
328 my $obj = $stash->get("circ_objects");
330 # ----------------------------------------------------------
331 # find the rules objects based on the rule names returned from
332 # the various scripts.
333 my $dur_req = $session->request(
334 "open-ils.storage.direct.config.rules.circ_duration.search.name",
335 $duration_rule->[0] );
337 my $rec_req = $session->request(
338 "open-ils.storage.direct.config.rules.recuring_fine.search.name",
339 $rec_fines_rule->[0] );
341 my $max_req = $session->request(
342 "open-ils.storage.direct.config.rules.max_fine.search.name",
343 $max_fines_rule->[0] );
345 my $duration = $dur_req->gather(1)->[0];
346 my $recurring = $rec_req->gather(1)->[0];
347 my $max = $max_req->gather(1)->[0];
349 my $copy = $circ_objects->{copy};
351 # build the new circ object
352 my $circ = build_circ_object($session, $copy, $duration_rule->[1],
353 $rec_fines_rule->[1], $duration, $recurring, $max );
355 return ($circ, $duration, $recurring, $max);
359 # ------------------------------------------------------------------
360 # Builds a new circ object
361 # ------------------------------------------------------------------
362 sub build_circ_object {
363 my( $session, $copy, $dur_level, $rec_level,
364 $duration, $recurring, $max ) = @_;
366 my $circ = new Fieldmapper::action::circulation;
368 $circ->circ_lib( $copy->circ_lib->id() );
369 if($dur_level == 1) {
370 $circ->duration( $duration->shrt );
371 } elsif($dur_level == 2) {
372 $circ->duration( $duration->normal );
373 } elsif($dur_level == 3) {
374 $circ->duration( $duration->extended );
377 if($rec_level == 1) {
378 $circ->recuring_fine( $recurring->low );
379 } elsif($rec_level == 2) {
380 $circ->recuring_fine( $recurring->normal );
381 } elsif($rec_level == 3) {
382 $circ->recuring_fine( $recurring->high );
385 $circ->duration_rule( $duration->name );
386 $circ->recuring_fine_rule( $recurring->name );
387 $circ->max_fine_rule( $max->name );
388 $circ->max_fine( $max->amount );
390 $circ->fine_interval($recurring->recurance_interval);
391 $circ->renewal_remaining( $duration->max_renewals );
392 $circ->target_copy( $copy->id );
393 $circ->usr( $circ_objects->{patron}->id );
399 __PACKAGE__->register_method(
401 api_name => "open-ils.circ.checkin.barcode",
405 my( $self, $user_session, $client, $barcode ) = @_;
411 my $session = $apputils->start_db_session();
413 warn "retrieving copy for checkin\n";
415 if(!$shelving_locations) {
416 my $sh_req = $session->request(
417 "open-ils.storage.direct.asset.copy_location.retrieve.all.atomic");
418 $shelving_locations = $sh_req->gather(1);
419 $shelving_locations =
420 { map { (''.$_->id => $_->name) } @$shelving_locations };
423 my $copy_req = $session->request(
424 "open-ils.storage.direct.asset.copy.search.barcode",
426 $copy = $copy_req->gather(1)->[0];
429 # find circ's where the transaction is still open for the
430 # given copy. should only be one.
431 warn "Retrieving circ for checking\n";
432 my $circ_req = $session->request(
433 "open-ils.storage.direct.action.circulation.search.atomic",
434 { target_copy => $copy->id, xact_finish => undef } );
436 my $circ = $circ_req->gather(1)->[0];
439 $err = "No circulation exists for the given barcode";
443 warn "Checking in circ ". $circ->id . "\n";
445 $circ->stop_fines("CHECKIN");
446 $circ->xact_finish("now");
448 my $cp_up = $session->request(
449 "open-ils.storage.direct.asset.copy.update",
453 my $ci_up = $session->request(
454 "open-ils.storage.direct.action.circulation.update",
458 $apputils->commit_db_session($session);
460 warn "Checkin succeeded\n";
465 $err = "Error checking in: $e";
469 return { record => undef, status => -1, text => $err };
473 my $record = $apputils->simple_scalar_request(
475 "open-ils.storage.fleshed.biblio.record_entry.retrieve_by_copy",
478 my $u = OpenILS::Utils::ModsParser->new();
479 $u->start_mods_batch( $record->marc() );
480 my $mods = $u->finish_mods_batch();
481 return { record => $mods, status => 0, text => "OK",
482 route_to => $shelving_locations->{$copy->location} };