]> git.evergreen-ils.org Git - Evergreen.git/blob - Open-ILS/src/perlmods/OpenILS/Application/Circ/Rules.pm
b9071a7db8c282ffb2550d689b1039b0342ff428
[Evergreen.git] / Open-ILS / src / perlmods / OpenILS / Application / Circ / Rules.pm
1 package OpenILS::Application::Circ::Rules;
2 use base qw/OpenSRF::Application/;
3 use strict; use warnings;
4
5 use OpenSRF::Utils::SettingsClient;
6 use OpenILS::Utils::Fieldmapper;
7
8 use Template qw(:template);
9 use Template::Stash; 
10
11 use Time::HiRes qw(time);
12 use OpenILS::Utils::ModsParser;
13
14 use OpenSRF::Utils;
15 use OpenSRF::EX qw(:try);
16
17 use OpenILS::Application::AppUtils;
18 my $apputils = "OpenILS::Application::AppUtils";
19 use Digest::MD5 qw(md5_hex);
20
21 # ----------------------------------------------------------------
22 # rules scripts
23 my $circ_script;
24 my $permission_script;
25 my $duration_script;
26 my $recurring_fines_script;
27 my $max_fines_script;
28 # ----------------------------------------------------------------
29
30
31 # data used for this circulation transaction
32 my $circ_objects = {};
33
34 # some static data from the database
35 my $copy_statuses;
36 my $patron_standings;
37 my $patron_profiles;
38 my $shelving_locations;
39
40 # template stash
41 my $stash;
42
43 # memcache for caching the circ objects
44 my $cache_handle;
45
46
47 sub initialize {
48         my $self = shift;
49         my $conf = OpenSRF::Utils::SettingsClient->new;
50
51         # ----------------------------------------------------------------
52         # set up the rules scripts
53         # ----------------------------------------------------------------
54         $circ_script = $conf->config_value(                                     
55                 "apps", "open-ils.circ","app_settings", "rules", "main");
56
57         $permission_script = $conf->config_value(                       
58                 "apps", "open-ils.circ","app_settings", "rules", "permission");
59
60         $duration_script = $conf->config_value(                 
61                 "apps", "open-ils.circ","app_settings", "rules", "duration");
62
63         $recurring_fines_script = $conf->config_value(  
64                 "apps", "open-ils.circ","app_settings", "rules", "recurring_fines");
65
66         $max_fines_script = $conf->config_value(                        
67                 "apps", "open-ils.circ","app_settings", "rules", "max_fines");
68
69
70         $cache_handle = OpenSRF::Utils::Cache->new();
71 }
72
73
74
75 # ----------------------------------------------------------------
76 # Collect all of the objects necessary for calculating the
77 # circ matrix.
78 # ----------------------------------------------------------------
79 sub gather_circ_objects {
80         my( $session, $barcode_string, $patron_id ) = @_;
81
82         throw OpenSRF::EX::ERROR 
83                 ("gather_circ_objects needs data")
84                         unless ($barcode_string and $patron_id);
85
86         warn "Gathering circ objects with barcode $barcode_string and patron id $patron_id\n";
87
88         
89
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);
93
94         if($circ_objects) { 
95                 $stash = Template::Stash->new(
96                         circ_objects                    => $circ_objects,
97                         result                                  => [],
98                         target_copy_status      => 1,
99                         );
100                 return;
101         }
102
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);
109                 $patron_standings =
110                         { map { (''.$_->id => $_->value) } @$patron_standings };
111         }
112
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);
118                 $patron_profiles =
119                         { map { (''.$_->id => $_->name) } @$patron_profiles };
120         }
121
122
123         my $copy_req    = $session->request(
124                 "open-ils.storage.fleshed.asset.copy.search.barcode", 
125                 $barcode_string );
126
127         my $patron_req  = $session->request(
128                 "open-ils.storage.direct.actor.user.retrieve", 
129                 $patron_id );
130
131         my $copy = $copy_req->gather(1)->[0];
132         $copy->status( $copy->status->name );
133         $circ_objects->{copy} = $copy;
134
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;
139
140         
141         my $title_req   = $session->request(
142                 "open-ils.storage.fleshed.biblio.record_entry.retrieve_by_copy",
143                 $circ_objects->{copy}->id() );
144
145         $circ_objects->{title} = $title_req->gather(1);
146
147         $cache_handle->put_cache( $cache_key, $circ_objects, 30 );
148
149         $stash = Template::Stash->new(
150                         circ_objects                    => $circ_objects,
151                         result                                  => [],
152                         target_copy_status      => 1,
153                         );
154
155 }
156
157
158
159 sub run_script {
160
161         my $result;
162
163         my $template = Template->new(
164                 { 
165                         STASH                   => $stash,
166                         ABSOLUTE                => 1, 
167                         OUTPUT          => \$result,
168                 }
169         );
170
171         my $status = $template->process($circ_script);
172
173         if(!$status) { 
174                 throw OpenSRF::EX::ERROR 
175                         ("Error processing circ script " .  $template->error()); 
176         }
177
178         warn "Script result: $result\n";
179 }
180
181
182
183
184 __PACKAGE__->register_method(
185         method  => "permit_circ",
186         api_name        => "open-ils.circ.permit_checkout",
187 );
188
189 sub permit_circ {
190         my( $self, $client, $user_session, $barcode, $user_id, $outstanding_count ) = @_;
191
192         $outstanding_count ||= 0;
193
194         my $session     = OpenSRF::AppSession->create("open-ils.storage");
195         
196         # collect items necessary for circ calculation
197         gather_circ_objects( $session, $barcode, $user_id );
198         
199         $stash->set("run_block", $permission_script);
200
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);
207
208         $stash->set("patron_copies", $summary->[0]  + $outstanding_count );
209         $stash->set("patron_fines", $summary->[1] );
210
211         # run the permissibility script
212         run_script();
213         my $obj = $stash->get("circ_objects");
214
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();
219
220         my $arr = $stash->get("result");
221         return { record => $mods, status => $arr->[0], text => $arr->[1] };
222
223 }
224
225
226 __PACKAGE__->register_method(
227         method  => "circulate",
228         api_name        => "open-ils.circ.checkout.barcode",
229 );
230
231 sub circulate {
232         my( $self, $client, $user_session, $barcode, $patron ) = @_;
233
234
235         my $session = $apputils->start_db_session();
236
237         gather_circ_objects( $session, $barcode, $patron );
238
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);
244         }
245
246         # put copy statuses into the stash
247         $stash->set("copy_statuses", $copy_statuses );
248
249         my $copy = $circ_objects->{copy};
250         my ($circ, $duration, $recurring, $max) =  run_circ_scripts($session);
251
252         # commit new circ object to db
253         my $commit = $session->request(
254                 "open-ils.storage.direct.action.circulation.create",
255                 $circ );
256         my $id = $commit->gather(1);
257
258         if(!$id) {
259                 throw OpenSRF::EX::ERROR 
260                         ("Error creating new circulation object");
261         }
262
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 );
267
268         # commit copy to db
269         my $copy_update = $session->request(
270                 "open-ils.storage.direct.asset.copy.update",
271                 $copy );
272         $copy_update->gather(1);
273
274         $apputils->commit_db_session($session);
275
276         # remove our circ object from the cache
277         $cache_handle->delete_cache("circ_object_" . md5_hex($barcode, $patron));
278
279         # re-retrieve the the committed circ object  
280         $circ = $apputils->simple_scalar_request(
281                 "open-ils.storage",
282                 "open-ils.storage.direct.action.circulation.retrieve",
283                 $id );
284
285
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);
290
291         my $due_date = 
292                 OpenSRF::Utils->interval_to_seconds( 
293                         $circ->duration ) + int(time());
294
295         $circ->due_date($due_date);
296
297         return $circ;
298
299 }
300
301
302
303 # runs the duration, recurring_fines, and max_fines scripts.
304 # builds the new circ object based on the rules returned from 
305 # these scripts. 
306 # returns (circ, duration_rule, recurring_fines_rule, max_fines_rule)
307 sub run_circ_scripts {
308         my $session = shift;
309
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);
315         run_script();
316         my $duration_rule = $stash->get("result");
317
318         $stash->set("run_block", $recurring_fines_script);
319         $stash->set("result", [] );
320         run_script();
321         my $rec_fines_rule = $stash->get("result");
322
323         $stash->set("run_block", $max_fines_script);
324         $stash->set("result", [] );
325         run_script();
326         my $max_fines_rule = $stash->get("result");
327
328         my $obj = $stash->get("circ_objects");
329
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] );
336
337         my $rec_req = $session->request(
338                 "open-ils.storage.direct.config.rules.recuring_fine.search.name",
339                 $rec_fines_rule->[0] );
340
341         my $max_req = $session->request(
342                 "open-ils.storage.direct.config.rules.max_fine.search.name",
343                 $max_fines_rule->[0] );
344
345         my $duration    = $dur_req->gather(1)->[0];
346         my $recurring   = $rec_req->gather(1)->[0];
347         my $max                 = $max_req->gather(1)->[0];
348
349         my $copy = $circ_objects->{copy};
350
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 );
354
355         return ($circ, $duration, $recurring, $max);
356
357 }
358
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 ) = @_;
365
366         my $circ = new Fieldmapper::action::circulation;
367
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 );
375         }
376
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 );
383         }
384
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 );
389
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 );
394
395         return $circ;
396
397 }
398
399 __PACKAGE__->register_method(
400         method  => "checkin",
401         api_name        => "open-ils.circ.checkin.barcode",
402 );
403
404 sub checkin {
405         my( $self, $user_session, $client, $barcode ) = @_;
406
407         my $err;
408         my $copy;
409
410         try {
411                 my $session = $apputils->start_db_session();
412         
413                 warn "retrieving copy for checkin\n";
414
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 };
421                 }
422         
423                 my $copy_req = $session->request(
424                         "open-ils.storage.direct.asset.copy.search.barcode", 
425                         $barcode );
426                 $copy = $copy_req->gather(1)->[0];
427                 $copy->status(0);
428         
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 } );
435         
436                 my $circ = $circ_req->gather(1)->[0];
437         
438                 if(!$circ) {
439                         $err = "No circulation exists for the given barcode";
440
441                 } else {
442         
443                         warn "Checking in circ ". $circ->id . "\n";
444                 
445                         $circ->stop_fines("CHECKIN");
446                         $circ->xact_finish("now");
447                 
448                         my $cp_up = $session->request(
449                                 "open-ils.storage.direct.asset.copy.update",
450                                 $copy );
451                         $cp_up->gather(1);
452                 
453                         my $ci_up = $session->request(
454                                 "open-ils.storage.direct.action.circulation.update",
455                                 $circ );
456                         $ci_up->gather(1);
457                 
458                         $apputils->commit_db_session($session);
459                 
460                         warn "Checkin succeeded\n";
461                 }
462         
463         } catch Error with {
464                 my $e = shift;
465                 $err = "Error checking in: $e";
466         };
467         
468         if($err) {
469                 return { record => undef, status => -1, text => $err };
470
471         } else {
472
473                 my $record = $apputils->simple_scalar_request(
474                         "open-ils.storage",
475                         "open-ils.storage.fleshed.biblio.record_entry.retrieve_by_copy",
476                         $copy->id() );
477
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} };
483         }
484
485         return 1;
486
487 }
488
489
490
491
492
493 1;