1 # ---------------------------------------------------------------
2 # Copyright (C) 2005 Georgia Public Library Service
3 # Bill Erickson <highfalutin@gmail.com>
5 # This program is free software; you can redistribute it and/or
6 # modify it under the terms of the GNU General Public License
7 # as published by the Free Software Foundation; either version 2
8 # of the License, or (at your option) any later version.
10 # This program is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 # GNU General Public License for more details.
14 # ---------------------------------------------------------------
16 package OpenILS::Application::Circ::Rules;
17 use base qw/OpenSRF::Application/;
18 use strict; use warnings;
20 use OpenSRF::Utils::SettingsClient;
21 use OpenILS::Utils::Fieldmapper;
24 use Template qw(:template);
27 use Time::HiRes qw(time);
28 use OpenILS::Utils::ModsParser;
31 use OpenSRF::EX qw(:try);
33 use OpenILS::Application::AppUtils;
34 my $apputils = "OpenILS::Application::AppUtils";
35 use Digest::MD5 qw(md5_hex);
37 # ----------------------------------------------------------------
40 my $permission_script;
42 my $recurring_fines_script;
44 # ----------------------------------------------------------------
47 # data used for this circulation transaction
48 my $circ_objects = {};
50 # some static data from the database
54 my $shelving_locations;
59 # memcache for caching the circ objects
63 use constant NO_COPY => 100;
67 my $conf = OpenSRF::Utils::SettingsClient->new;
69 # ----------------------------------------------------------------
70 # set up the rules scripts
71 # ----------------------------------------------------------------
72 $circ_script = $conf->config_value(
73 "apps", "open-ils.circ","app_settings", "rules", "main");
75 $permission_script = $conf->config_value(
76 "apps", "open-ils.circ","app_settings", "rules", "permission");
78 $duration_script = $conf->config_value(
79 "apps", "open-ils.circ","app_settings", "rules", "duration");
81 $recurring_fines_script = $conf->config_value(
82 "apps", "open-ils.circ","app_settings", "rules", "recurring_fines");
84 $max_fines_script = $conf->config_value(
85 "apps", "open-ils.circ","app_settings", "rules", "max_fines");
88 $cache_handle = OpenSRF::Utils::Cache->new();
93 # ----------------------------------------------------------------
94 # Collect all of the objects necessary for calculating the
96 # ----------------------------------------------------------------
97 sub gather_circ_objects {
98 my( $session, $barcode_string, $patron_id ) = @_;
100 throw OpenSRF::EX::ERROR
101 ("gather_circ_objects needs data")
102 unless ($barcode_string and $patron_id);
104 warn "Gathering circ objects with barcode $barcode_string and patron id $patron_id\n";
108 # see if all of the circ objects are in cache
109 my $cache_key = "circ_object_" . md5_hex( $barcode_string, $patron_id );
110 $circ_objects = $cache_handle->get_cache($cache_key);
113 $stash = Template::Stash->new(
114 circ_objects => $circ_objects,
116 target_copy_status => 1,
121 # grab the patron standing list of we don't already have it
122 # only necessary if the circ objects have not been built yet
123 if(!$patron_standings) {
124 my $standing_req = $session->request(
125 "open-ils.storage.direct.config.standing.retrieve.all.atomic");
126 $patron_standings = $standing_req->gather(1);
128 { map { (''.$_->id => $_->value) } @$patron_standings };
131 # grab patron profiles
132 if(!$patron_profiles) {
133 my $profile_req = $session->request(
134 "open-ils.storage.direct.actor.profile.retrieve.all.atomic");
135 $patron_profiles = $profile_req->gather(1);
137 { map { (''.$_->id => $_->name) } @$patron_profiles };
141 my $copy_req = $session->request(
142 "open-ils.storage.fleshed.asset.copy.search.barcode.atomic",
145 my $patron_req = $session->request(
146 "open-ils.storage.direct.actor.user.retrieve",
149 my $copy = $copy_req->gather(1)->[0];
150 if(!$copy) { return NO_COPY; }
152 $copy->status( $copy->status->name );
153 $circ_objects->{copy} = $copy;
155 my $patron = $patron_req->gather(1);
156 $patron->standing($patron_standings->{$patron->standing()});
157 $patron->profile( $patron_profiles->{$patron->profile});
158 $circ_objects->{patron} = $patron;
161 my $title_req = $session->request(
162 "open-ils.storage.fleshed.biblio.record_entry.retrieve_by_copy",
163 $circ_objects->{copy}->id() );
165 $circ_objects->{title} = $title_req->gather(1);
167 $cache_handle->put_cache( $cache_key, $circ_objects, 30 );
169 $stash = Template::Stash->new(
170 circ_objects => $circ_objects,
172 target_copy_status => 1,
183 my $template = Template->new(
191 my $status = $template->process($circ_script);
194 throw OpenSRF::EX::ERROR
195 ("Error processing circ script " . $template->error());
198 warn "Script result: $result\n";
204 __PACKAGE__->register_method(
205 method => "permit_circ",
206 api_name => "open-ils.circ.permit_checkout",
210 my( $self, $client, $user_session, $barcode, $user_id, $outstanding_count ) = @_;
212 $outstanding_count ||= 0;
214 my $session = OpenSRF::AppSession->create("open-ils.storage");
216 # collect items necessary for circ calculation
217 my $status = gather_circ_objects( $session, $barcode, $user_id );
219 if( $status == NO_COPY ) {
220 return { record => undef,
222 text => "No copy available with barcode $barcode"
228 $stash->set("run_block", $permission_script);
230 # grab the number of copies checked out by the patron as
231 # well as the total fines
232 my $summary_req = $session->request(
233 "open-ils.storage.action.circulation.patron_summary",
234 $stash->get("circ_objects")->{patron}->id );
235 my $summary = $summary_req->gather(1);
237 $stash->set("patron_copies", $summary->[0] + $outstanding_count );
238 $stash->set("patron_fines", $summary->[1] );
240 # run the permissibility script
242 my $obj = $stash->get("circ_objects");
244 # turn the biblio record into a friendly object
245 my $u = OpenILS::Utils::ModsParser->new();
246 $u->start_mods_batch( $obj->{title}->marc() );
247 my $mods = $u->finish_mods_batch();
249 my $arr = $stash->get("result");
250 return { record => $mods, status => $arr->[0], text => $arr->[1] };
255 __PACKAGE__->register_method(
256 method => "circulate",
257 api_name => "open-ils.circ.checkout.barcode",
261 my( $self, $client, $user_session, $barcode, $patron ) = @_;
264 my $session = $apputils->start_db_session();
266 gather_circ_objects( $session, $barcode, $patron );
268 # grab the copy statuses if we don't already have them
269 if(!$copy_statuses) {
270 my $csreq = $session->request(
271 "open-ils.storage.direct.config.copy_status.retrieve.all.atomic" );
272 $copy_statuses = $csreq->gather(1);
275 # put copy statuses into the stash
276 $stash->set("copy_statuses", $copy_statuses );
278 my $copy = $circ_objects->{copy};
279 my ($circ, $duration, $recurring, $max) = run_circ_scripts($session);
281 # commit new circ object to db
282 my $commit = $session->request(
283 "open-ils.storage.direct.action.circulation.create",
285 my $id = $commit->gather(1);
288 throw OpenSRF::EX::ERROR
289 ("Error creating new circulation object");
292 # update the copy with the new circ
293 $copy->status( $stash->get("target_copy_status") );
294 $copy->location( $copy->location->id );
295 $copy->circ_lib( $copy->circ_lib->id );
298 my $copy_update = $session->request(
299 "open-ils.storage.direct.asset.copy.update",
301 $copy_update->gather(1);
303 $apputils->commit_db_session($session);
305 # remove our circ object from the cache
306 $cache_handle->delete_cache("circ_object_" . md5_hex($barcode, $patron));
308 # re-retrieve the the committed circ object
309 $circ = $apputils->simple_scalar_request(
311 "open-ils.storage.direct.action.circulation.retrieve",
315 # push the rules and due date into the circ object
316 $circ->duration_rule($duration);
317 $circ->max_fine_rule($max);
318 $circ->recuring_fine_rule($recurring);
322 # OpenSRF::Utils->interval_to_seconds(
323 # $circ->duration ) + int(time());
325 # this comes from an earlier setting now
326 # $circ->due_date($due_date);
334 # runs the duration, recurring_fines, and max_fines scripts.
335 # builds the new circ object based on the rules returned from
337 # returns (circ, duration_rule, recurring_fines_rule, max_fines_rule)
338 sub run_circ_scripts {
341 # go through all of the scripts and process
342 # each script returns
343 # [ rule_name, level (appropriate to the script) ]
344 $stash->set("result", [] );
345 $stash->set("run_block", $duration_script);
347 my $duration_rule = $stash->get("result");
349 $stash->set("run_block", $recurring_fines_script);
350 $stash->set("result", [] );
352 my $rec_fines_rule = $stash->get("result");
354 $stash->set("run_block", $max_fines_script);
355 $stash->set("result", [] );
357 my $max_fines_rule = $stash->get("result");
359 my $obj = $stash->get("circ_objects");
361 # ----------------------------------------------------------
362 # find the rules objects based on the rule names returned from
363 # the various scripts.
364 my $dur_req = $session->request(
365 "open-ils.storage.direct.config.rules.circ_duration.search.name.atomic",
366 $duration_rule->[0] );
368 my $rec_req = $session->request(
369 "open-ils.storage.direct.config.rules.recuring_fine.search.name.atomic",
370 $rec_fines_rule->[0] );
372 my $max_req = $session->request(
373 "open-ils.storage.direct.config.rules.max_fine.search.name.atomic",
374 $max_fines_rule->[0] );
376 my $duration = $dur_req->gather(1)->[0];
377 my $recurring = $rec_req->gather(1)->[0];
378 my $max = $max_req->gather(1)->[0];
380 my $copy = $circ_objects->{copy};
383 warn "Building a new circulation object with\n".
384 "=> copy " . Dumper($copy) .
385 "=> duration_rule " . Dumper($duration_rule) .
386 "=> rec_files_rule " . Dumper($rec_fines_rule) .
387 "=> duration " . Dumper($duration) .
388 "=> recurring " . Dumper($recurring) .
389 "=> max " . Dumper($max);
392 # build the new circ object
393 my $circ = build_circ_object($session, $copy, $duration_rule->[1],
394 $rec_fines_rule->[1], $duration, $recurring, $max );
396 return ($circ, $duration, $recurring, $max);
400 # ------------------------------------------------------------------
401 # Builds a new circ object
402 # ------------------------------------------------------------------
403 sub build_circ_object {
404 my( $session, $copy, $dur_level, $rec_level,
405 $duration, $recurring, $max ) = @_;
407 my $circ = new Fieldmapper::action::circulation;
409 $circ->circ_lib( $copy->circ_lib->id() );
410 if($dur_level == 1) {
411 $circ->duration( $duration->shrt );
412 } elsif($dur_level == 2) {
413 $circ->duration( $duration->normal );
414 } elsif($dur_level == 3) {
415 $circ->duration( $duration->extended );
418 if($rec_level == 1) {
419 $circ->recuring_fine( $recurring->low );
420 } elsif($rec_level == 2) {
421 $circ->recuring_fine( $recurring->normal );
422 } elsif($rec_level == 3) {
423 $circ->recuring_fine( $recurring->high );
426 $circ->duration_rule( $duration->name );
427 $circ->recuring_fine_rule( $recurring->name );
428 $circ->max_fine_rule( $max->name );
429 $circ->max_fine( $max->amount );
431 $circ->fine_interval($recurring->recurance_interval);
432 $circ->renewal_remaining( $duration->max_renewals );
433 $circ->target_copy( $copy->id );
434 $circ->usr( $circ_objects->{patron}->id );
440 __PACKAGE__->register_method(
442 api_name => "open-ils.circ.checkin.barcode",
446 my( $self, $client, $user_session, $barcode ) = @_;
452 my $session = $apputils->start_db_session();
454 warn "retrieving copy for checkin\n";
456 if(!$shelving_locations) {
457 my $sh_req = $session->request(
458 "open-ils.storage.direct.asset.copy_location.retrieve.all.atomic");
459 $shelving_locations = $sh_req->gather(1);
460 $shelving_locations =
461 { map { (''.$_->id => $_->name) } @$shelving_locations };
464 my $copy_req = $session->request(
465 "open-ils.storage.direct.asset.copy.search.barcode.atomic",
467 $copy = $copy_req->gather(1)->[0];
469 $client->respond_complete(
470 OpenILS::EX->new("UNKNOWN_BARCODE")->ex);
475 # find circ's where the transaction is still open for the
476 # given copy. should only be one.
477 warn "Retrieving circ for checking\n";
478 my $circ_req = $session->request(
479 "open-ils.storage.direct.action.circulation.search.atomic.atomic",
480 { target_copy => $copy->id, xact_finish => undef } );
482 my $circ = $circ_req->gather(1)->[0];
485 $err = "No circulation exists for the given barcode";
489 warn "Checking in circ ". $circ->id . "\n";
491 $circ->stop_fines("CHECKIN");
492 $circ->xact_finish("now");
494 my $cp_up = $session->request(
495 "open-ils.storage.direct.asset.copy.update",
499 my $ci_up = $session->request(
500 "open-ils.storage.direct.action.circulation.update",
504 $apputils->commit_db_session($session);
506 warn "Checkin succeeded\n";
511 $err = "Error checking in: $e";
515 return { record => undef, status => -1, text => $err };
519 my $record = $apputils->simple_scalar_request(
521 "open-ils.storage.fleshed.biblio.record_entry.retrieve_by_copy",
524 my $u = OpenILS::Utils::ModsParser->new();
525 $u->start_mods_batch( $record->marc() );
526 my $mods = $u->finish_mods_batch();
527 return { record => $mods, status => 0, text => "OK",
528 route_to => $shelving_locations->{$copy->location} };