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;
9 use Template qw(:template);
12 use Time::HiRes qw(time);
13 use OpenILS::Utils::ModsParser;
16 use OpenSRF::EX qw(:try);
18 use OpenILS::Application::AppUtils;
19 my $apputils = "OpenILS::Application::AppUtils";
20 use Digest::MD5 qw(md5_hex);
22 # ----------------------------------------------------------------
25 my $permission_script;
27 my $recurring_fines_script;
29 # ----------------------------------------------------------------
32 # data used for this circulation transaction
33 my $circ_objects = {};
35 # some static data from the database
39 my $shelving_locations;
44 # memcache for caching the circ objects
48 use constant NO_COPY => 100;
52 my $conf = OpenSRF::Utils::SettingsClient->new;
54 # ----------------------------------------------------------------
55 # set up the rules scripts
56 # ----------------------------------------------------------------
57 $circ_script = $conf->config_value(
58 "apps", "open-ils.circ","app_settings", "rules", "main");
60 $permission_script = $conf->config_value(
61 "apps", "open-ils.circ","app_settings", "rules", "permission");
63 $duration_script = $conf->config_value(
64 "apps", "open-ils.circ","app_settings", "rules", "duration");
66 $recurring_fines_script = $conf->config_value(
67 "apps", "open-ils.circ","app_settings", "rules", "recurring_fines");
69 $max_fines_script = $conf->config_value(
70 "apps", "open-ils.circ","app_settings", "rules", "max_fines");
73 $cache_handle = OpenSRF::Utils::Cache->new();
78 # ----------------------------------------------------------------
79 # Collect all of the objects necessary for calculating the
81 # ----------------------------------------------------------------
82 sub gather_circ_objects {
83 my( $session, $barcode_string, $patron_id ) = @_;
85 throw OpenSRF::EX::ERROR
86 ("gather_circ_objects needs data")
87 unless ($barcode_string and $patron_id);
89 warn "Gathering circ objects with barcode $barcode_string and patron id $patron_id\n";
93 # see if all of the circ objects are in cache
94 my $cache_key = "circ_object_" . md5_hex( $barcode_string, $patron_id );
95 $circ_objects = $cache_handle->get_cache($cache_key);
98 $stash = Template::Stash->new(
99 circ_objects => $circ_objects,
101 target_copy_status => 1,
106 # grab the patron standing list of we don't already have it
107 # only necessary if the circ objects have not been built yet
108 if(!$patron_standings) {
109 my $standing_req = $session->request(
110 "open-ils.storage.direct.config.standing.retrieve.all.atomic");
111 $patron_standings = $standing_req->gather(1);
113 { map { (''.$_->id => $_->value) } @$patron_standings };
116 # grab patron profiles
117 if(!$patron_profiles) {
118 my $profile_req = $session->request(
119 "open-ils.storage.direct.actor.profile.retrieve.all.atomic");
120 $patron_profiles = $profile_req->gather(1);
122 { map { (''.$_->id => $_->name) } @$patron_profiles };
126 my $copy_req = $session->request(
127 "open-ils.storage.fleshed.asset.copy.search.barcode",
130 my $patron_req = $session->request(
131 "open-ils.storage.direct.actor.user.retrieve",
134 my $copy = $copy_req->gather(1)->[0];
135 if(!$copy) { return NO_COPY; }
137 $copy->status( $copy->status->name );
138 $circ_objects->{copy} = $copy;
140 my $patron = $patron_req->gather(1);
141 $patron->standing($patron_standings->{$patron->standing()});
142 $patron->profile( $patron_profiles->{$patron->profile});
143 $circ_objects->{patron} = $patron;
146 my $title_req = $session->request(
147 "open-ils.storage.fleshed.biblio.record_entry.retrieve_by_copy",
148 $circ_objects->{copy}->id() );
150 $circ_objects->{title} = $title_req->gather(1);
152 $cache_handle->put_cache( $cache_key, $circ_objects, 30 );
154 $stash = Template::Stash->new(
155 circ_objects => $circ_objects,
157 target_copy_status => 1,
168 my $template = Template->new(
176 my $status = $template->process($circ_script);
179 throw OpenSRF::EX::ERROR
180 ("Error processing circ script " . $template->error());
183 warn "Script result: $result\n";
189 __PACKAGE__->register_method(
190 method => "permit_circ",
191 api_name => "open-ils.circ.permit_checkout",
195 my( $self, $client, $user_session, $barcode, $user_id, $outstanding_count ) = @_;
197 $outstanding_count ||= 0;
199 my $session = OpenSRF::AppSession->create("open-ils.storage");
201 # collect items necessary for circ calculation
202 my $status = gather_circ_objects( $session, $barcode, $user_id );
204 if( $status == NO_COPY ) {
205 return { record => undef,
207 text => "No copy available with barcode $barcode"
213 $stash->set("run_block", $permission_script);
215 # grab the number of copies checked out by the patron as
216 # well as the total fines
217 my $summary_req = $session->request(
218 "open-ils.storage.action.circulation.patron_summary",
219 $stash->get("circ_objects")->{patron}->id );
220 my $summary = $summary_req->gather(1);
222 $stash->set("patron_copies", $summary->[0] + $outstanding_count );
223 $stash->set("patron_fines", $summary->[1] );
225 # run the permissibility script
227 my $obj = $stash->get("circ_objects");
229 # turn the biblio record into a friendly object
230 my $u = OpenILS::Utils::ModsParser->new();
231 $u->start_mods_batch( $obj->{title}->marc() );
232 my $mods = $u->finish_mods_batch();
234 my $arr = $stash->get("result");
235 return { record => $mods, status => $arr->[0], text => $arr->[1] };
240 __PACKAGE__->register_method(
241 method => "circulate",
242 api_name => "open-ils.circ.checkout.barcode",
246 my( $self, $client, $user_session, $barcode, $patron ) = @_;
249 my $session = $apputils->start_db_session();
251 gather_circ_objects( $session, $barcode, $patron );
253 # grab the copy statuses if we don't already have them
254 if(!$copy_statuses) {
255 my $csreq = $session->request(
256 "open-ils.storage.direct.config.copy_status.retrieve.all.atomic" );
257 $copy_statuses = $csreq->gather(1);
260 # put copy statuses into the stash
261 $stash->set("copy_statuses", $copy_statuses );
263 my $copy = $circ_objects->{copy};
264 my ($circ, $duration, $recurring, $max) = run_circ_scripts($session);
266 # commit new circ object to db
267 my $commit = $session->request(
268 "open-ils.storage.direct.action.circulation.create",
270 my $id = $commit->gather(1);
273 throw OpenSRF::EX::ERROR
274 ("Error creating new circulation object");
277 # update the copy with the new circ
278 $copy->status( $stash->get("target_copy_status") );
279 $copy->location( $copy->location->id );
280 $copy->circ_lib( $copy->circ_lib->id );
283 my $copy_update = $session->request(
284 "open-ils.storage.direct.asset.copy.update",
286 $copy_update->gather(1);
288 $apputils->commit_db_session($session);
290 # remove our circ object from the cache
291 $cache_handle->delete_cache("circ_object_" . md5_hex($barcode, $patron));
293 # re-retrieve the the committed circ object
294 $circ = $apputils->simple_scalar_request(
296 "open-ils.storage.direct.action.circulation.retrieve",
300 # push the rules and due date into the circ object
301 $circ->duration_rule($duration);
302 $circ->max_fine_rule($max);
303 $circ->recuring_fine_rule($recurring);
306 OpenSRF::Utils->interval_to_seconds(
307 $circ->duration ) + int(time());
309 $circ->due_date($due_date);
317 # runs the duration, recurring_fines, and max_fines scripts.
318 # builds the new circ object based on the rules returned from
320 # returns (circ, duration_rule, recurring_fines_rule, max_fines_rule)
321 sub run_circ_scripts {
324 # go through all of the scripts and process
325 # each script returns
326 # [ rule_name, level (appropriate to the script) ]
327 $stash->set("result", [] );
328 $stash->set("run_block", $duration_script);
330 my $duration_rule = $stash->get("result");
332 $stash->set("run_block", $recurring_fines_script);
333 $stash->set("result", [] );
335 my $rec_fines_rule = $stash->get("result");
337 $stash->set("run_block", $max_fines_script);
338 $stash->set("result", [] );
340 my $max_fines_rule = $stash->get("result");
342 my $obj = $stash->get("circ_objects");
344 # ----------------------------------------------------------
345 # find the rules objects based on the rule names returned from
346 # the various scripts.
347 my $dur_req = $session->request(
348 "open-ils.storage.direct.config.rules.circ_duration.search.name",
349 $duration_rule->[0] );
351 my $rec_req = $session->request(
352 "open-ils.storage.direct.config.rules.recuring_fine.search.name",
353 $rec_fines_rule->[0] );
355 my $max_req = $session->request(
356 "open-ils.storage.direct.config.rules.max_fine.search.name",
357 $max_fines_rule->[0] );
359 my $duration = $dur_req->gather(1)->[0];
360 my $recurring = $rec_req->gather(1)->[0];
361 my $max = $max_req->gather(1)->[0];
363 my $copy = $circ_objects->{copy};
366 warn "Building a new circulation object with\n".
367 "=> copy " . Dumper($copy) .
368 "=> duration_rule " . Dumper($duration_rule) .
369 "=> rec_files_rule " . Dumper($rec_fines_rule) .
370 "=> duration " . Dumper($duration) .
371 "=> recurring " . Dumper($recurring) .
372 "=> max " . Dumper($max);
375 # build the new circ object
376 my $circ = build_circ_object($session, $copy, $duration_rule->[1],
377 $rec_fines_rule->[1], $duration, $recurring, $max );
379 return ($circ, $duration, $recurring, $max);
383 # ------------------------------------------------------------------
384 # Builds a new circ object
385 # ------------------------------------------------------------------
386 sub build_circ_object {
387 my( $session, $copy, $dur_level, $rec_level,
388 $duration, $recurring, $max ) = @_;
390 my $circ = new Fieldmapper::action::circulation;
392 $circ->circ_lib( $copy->circ_lib->id() );
393 if($dur_level == 1) {
394 $circ->duration( $duration->shrt );
395 } elsif($dur_level == 2) {
396 $circ->duration( $duration->normal );
397 } elsif($dur_level == 3) {
398 $circ->duration( $duration->extended );
401 if($rec_level == 1) {
402 $circ->recuring_fine( $recurring->low );
403 } elsif($rec_level == 2) {
404 $circ->recuring_fine( $recurring->normal );
405 } elsif($rec_level == 3) {
406 $circ->recuring_fine( $recurring->high );
409 $circ->duration_rule( $duration->name );
410 $circ->recuring_fine_rule( $recurring->name );
411 $circ->max_fine_rule( $max->name );
412 $circ->max_fine( $max->amount );
414 $circ->fine_interval($recurring->recurance_interval);
415 $circ->renewal_remaining( $duration->max_renewals );
416 $circ->target_copy( $copy->id );
417 $circ->usr( $circ_objects->{patron}->id );
423 __PACKAGE__->register_method(
425 api_name => "open-ils.circ.checkin.barcode",
429 my( $self, $client, $user_session, $barcode ) = @_;
435 my $session = $apputils->start_db_session();
437 warn "retrieving copy for checkin\n";
439 if(!$shelving_locations) {
440 my $sh_req = $session->request(
441 "open-ils.storage.direct.asset.copy_location.retrieve.all.atomic");
442 $shelving_locations = $sh_req->gather(1);
443 $shelving_locations =
444 { map { (''.$_->id => $_->name) } @$shelving_locations };
447 my $copy_req = $session->request(
448 "open-ils.storage.direct.asset.copy.search.barcode",
450 $copy = $copy_req->gather(1)->[0];
452 $client->respond_complete(
453 OpenILS::EX->new("UNKNOWN_BARCODE")->ex);
458 # find circ's where the transaction is still open for the
459 # given copy. should only be one.
460 warn "Retrieving circ for checking\n";
461 my $circ_req = $session->request(
462 "open-ils.storage.direct.action.circulation.search.atomic",
463 { target_copy => $copy->id, xact_finish => undef } );
465 my $circ = $circ_req->gather(1)->[0];
468 $err = "No circulation exists for the given barcode";
472 warn "Checking in circ ". $circ->id . "\n";
474 $circ->stop_fines("CHECKIN");
475 $circ->xact_finish("now");
477 my $cp_up = $session->request(
478 "open-ils.storage.direct.asset.copy.update",
482 my $ci_up = $session->request(
483 "open-ils.storage.direct.action.circulation.update",
487 $apputils->commit_db_session($session);
489 warn "Checkin succeeded\n";
494 $err = "Error checking in: $e";
498 return { record => undef, status => -1, text => $err };
502 my $record = $apputils->simple_scalar_request(
504 "open-ils.storage.fleshed.biblio.record_entry.retrieve_by_copy",
507 my $u = OpenILS::Utils::ModsParser->new();
508 $u->start_mods_batch( $record->marc() );
509 my $mods = $u->finish_mods_batch();
510 return { record => $mods, status => 0, text => "OK",
511 route_to => $shelving_locations->{$copy->location} };