demo, coming fast
[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 use OpenILS::EX;
8
9 use Template qw(:template);
10 use Template::Stash; 
11
12 use Time::HiRes qw(time);
13 use OpenILS::Utils::ModsParser;
14
15 use OpenSRF::Utils;
16 use OpenSRF::EX qw(:try);
17
18 use OpenILS::Application::AppUtils;
19 my $apputils = "OpenILS::Application::AppUtils";
20 use Digest::MD5 qw(md5_hex);
21
22 # ----------------------------------------------------------------
23 # rules scripts
24 my $circ_script;
25 my $permission_script;
26 my $duration_script;
27 my $recurring_fines_script;
28 my $max_fines_script;
29 # ----------------------------------------------------------------
30
31
32 # data used for this circulation transaction
33 my $circ_objects = {};
34
35 # some static data from the database
36 my $copy_statuses;
37 my $patron_standings;
38 my $patron_profiles;
39 my $shelving_locations;
40
41 # template stash
42 my $stash;
43
44 # memcache for caching the circ objects
45 my $cache_handle;
46
47
48 use constant NO_COPY => 100;
49
50 sub initialize {
51         my $self = shift;
52         my $conf = OpenSRF::Utils::SettingsClient->new;
53
54         # ----------------------------------------------------------------
55         # set up the rules scripts
56         # ----------------------------------------------------------------
57         $circ_script = $conf->config_value(                                     
58                 "apps", "open-ils.circ","app_settings", "rules", "main");
59
60         $permission_script = $conf->config_value(                       
61                 "apps", "open-ils.circ","app_settings", "rules", "permission");
62
63         $duration_script = $conf->config_value(                 
64                 "apps", "open-ils.circ","app_settings", "rules", "duration");
65
66         $recurring_fines_script = $conf->config_value(  
67                 "apps", "open-ils.circ","app_settings", "rules", "recurring_fines");
68
69         $max_fines_script = $conf->config_value(                        
70                 "apps", "open-ils.circ","app_settings", "rules", "max_fines");
71
72
73         $cache_handle = OpenSRF::Utils::Cache->new();
74 }
75
76
77
78 # ----------------------------------------------------------------
79 # Collect all of the objects necessary for calculating the
80 # circ matrix.
81 # ----------------------------------------------------------------
82 sub gather_circ_objects {
83         my( $session, $barcode_string, $patron_id ) = @_;
84
85         throw OpenSRF::EX::ERROR 
86                 ("gather_circ_objects needs data")
87                         unless ($barcode_string and $patron_id);
88
89         warn "Gathering circ objects with barcode $barcode_string and patron id $patron_id\n";
90
91         
92
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);
96
97         if($circ_objects) { 
98                 $stash = Template::Stash->new(
99                         circ_objects                    => $circ_objects,
100                         result                                  => [],
101                         target_copy_status      => 1,
102                         );
103                 return;
104         }
105
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);
112                 $patron_standings =
113                         { map { (''.$_->id => $_->value) } @$patron_standings };
114         }
115
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);
121                 $patron_profiles =
122                         { map { (''.$_->id => $_->name) } @$patron_profiles };
123         }
124
125
126         my $copy_req    = $session->request(
127                 "open-ils.storage.fleshed.asset.copy.search.barcode", 
128                 $barcode_string );
129
130         my $patron_req  = $session->request(
131                 "open-ils.storage.direct.actor.user.retrieve", 
132                 $patron_id );
133
134         my $copy = $copy_req->gather(1)->[0];
135         if(!$copy) { return NO_COPY; }
136
137         $copy->status( $copy->status->name );
138         $circ_objects->{copy} = $copy;
139
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;
144
145         
146         my $title_req   = $session->request(
147                 "open-ils.storage.fleshed.biblio.record_entry.retrieve_by_copy",
148                 $circ_objects->{copy}->id() );
149
150         $circ_objects->{title} = $title_req->gather(1);
151
152         $cache_handle->put_cache( $cache_key, $circ_objects, 30 );
153
154         $stash = Template::Stash->new(
155                         circ_objects                    => $circ_objects,
156                         result                                  => [],
157                         target_copy_status      => 1,
158                         );
159
160 }
161
162
163
164 sub run_script {
165
166         my $result;
167
168         my $template = Template->new(
169                 { 
170                         STASH                   => $stash,
171                         ABSOLUTE                => 1, 
172                         OUTPUT          => \$result,
173                 }
174         );
175
176         my $status = $template->process($circ_script);
177
178         if(!$status) { 
179                 throw OpenSRF::EX::ERROR 
180                         ("Error processing circ script " .  $template->error()); 
181         }
182
183         warn "Script result: $result\n";
184 }
185
186
187
188
189 __PACKAGE__->register_method(
190         method  => "permit_circ",
191         api_name        => "open-ils.circ.permit_checkout",
192 );
193
194 sub permit_circ {
195         my( $self, $client, $user_session, $barcode, $user_id, $outstanding_count ) = @_;
196
197         $outstanding_count ||= 0;
198
199         my $session     = OpenSRF::AppSession->create("open-ils.storage");
200         
201         # collect items necessary for circ calculation
202         my $status = gather_circ_objects( $session, $barcode, $user_id );
203
204         if( $status == NO_COPY ) {
205                 return { record => undef, 
206                         status => NO_COPY, 
207                         text => "No copy available with barcode $barcode"
208                 };
209         }
210
211
212         
213         $stash->set("run_block", $permission_script);
214
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);
221
222         $stash->set("patron_copies", $summary->[0]  + $outstanding_count );
223         $stash->set("patron_fines", $summary->[1] );
224
225         # run the permissibility script
226         run_script();
227         my $obj = $stash->get("circ_objects");
228
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();
233
234         my $arr = $stash->get("result");
235         return { record => $mods, status => $arr->[0], text => $arr->[1] };
236
237 }
238
239
240 __PACKAGE__->register_method(
241         method  => "circulate",
242         api_name        => "open-ils.circ.checkout.barcode",
243 );
244
245 sub circulate {
246         my( $self, $client, $user_session, $barcode, $patron ) = @_;
247
248
249         my $session = $apputils->start_db_session();
250
251         gather_circ_objects( $session, $barcode, $patron );
252
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);
258         }
259
260         # put copy statuses into the stash
261         $stash->set("copy_statuses", $copy_statuses );
262
263         my $copy = $circ_objects->{copy};
264         my ($circ, $duration, $recurring, $max) =  run_circ_scripts($session);
265
266         # commit new circ object to db
267         my $commit = $session->request(
268                 "open-ils.storage.direct.action.circulation.create",
269                 $circ );
270         my $id = $commit->gather(1);
271
272         if(!$id) {
273                 throw OpenSRF::EX::ERROR 
274                         ("Error creating new circulation object");
275         }
276
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 );
281
282         # commit copy to db
283         my $copy_update = $session->request(
284                 "open-ils.storage.direct.asset.copy.update",
285                 $copy );
286         $copy_update->gather(1);
287
288         $apputils->commit_db_session($session);
289
290         # remove our circ object from the cache
291         $cache_handle->delete_cache("circ_object_" . md5_hex($barcode, $patron));
292
293         # re-retrieve the the committed circ object  
294         $circ = $apputils->simple_scalar_request(
295                 "open-ils.storage",
296                 "open-ils.storage.direct.action.circulation.retrieve",
297                 $id );
298
299
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);
304
305         my $due_date = 
306                 OpenSRF::Utils->interval_to_seconds( 
307                         $circ->duration ) + int(time());
308
309         $circ->due_date($due_date);
310
311         return $circ;
312
313 }
314
315
316
317 # runs the duration, recurring_fines, and max_fines scripts.
318 # builds the new circ object based on the rules returned from 
319 # these scripts. 
320 # returns (circ, duration_rule, recurring_fines_rule, max_fines_rule)
321 sub run_circ_scripts {
322         my $session = shift;
323
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);
329         run_script();
330         my $duration_rule = $stash->get("result");
331
332         $stash->set("run_block", $recurring_fines_script);
333         $stash->set("result", [] );
334         run_script();
335         my $rec_fines_rule = $stash->get("result");
336
337         $stash->set("run_block", $max_fines_script);
338         $stash->set("result", [] );
339         run_script();
340         my $max_fines_rule = $stash->get("result");
341
342         my $obj = $stash->get("circ_objects");
343
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] );
350
351         my $rec_req = $session->request(
352                 "open-ils.storage.direct.config.rules.recuring_fine.search.name",
353                 $rec_fines_rule->[0] );
354
355         my $max_req = $session->request(
356                 "open-ils.storage.direct.config.rules.max_fine.search.name",
357                 $max_fines_rule->[0] );
358
359         my $duration    = $dur_req->gather(1)->[0];
360         my $recurring   = $rec_req->gather(1)->[0];
361         my $max                 = $max_req->gather(1)->[0];
362
363         my $copy = $circ_objects->{copy};
364
365         use Data::Dumper;
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);
373
374
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 );
378
379         return ($circ, $duration, $recurring, $max);
380
381 }
382
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 ) = @_;
389
390         my $circ = new Fieldmapper::action::circulation;
391
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 );
399         }
400
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 );
407         }
408
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 );
413
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 );
418
419         return $circ;
420
421 }
422
423 __PACKAGE__->register_method(
424         method  => "checkin",
425         api_name        => "open-ils.circ.checkin.barcode",
426 );
427
428 sub checkin {
429         my( $self, $client, $user_session, $barcode ) = @_;
430
431         my $err;
432         my $copy;
433
434         try {
435                 my $session = $apputils->start_db_session();
436         
437                 warn "retrieving copy for checkin\n";
438
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 };
445                 }
446         
447                 my $copy_req = $session->request(
448                         "open-ils.storage.direct.asset.copy.search.barcode", 
449                         $barcode );
450                 $copy = $copy_req->gather(1)->[0];
451                 if(!$copy) {
452                         $client->respond_complete(
453                                         OpenILS::EX->new("UNKNOWN_BARCODE")->ex);
454                 }
455
456                 $copy->status(0);
457         
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 } );
464         
465                 my $circ = $circ_req->gather(1)->[0];
466         
467                 if(!$circ) {
468                         $err = "No circulation exists for the given barcode";
469
470                 } else {
471         
472                         warn "Checking in circ ". $circ->id . "\n";
473                 
474                         $circ->stop_fines("CHECKIN");
475                         $circ->xact_finish("now");
476                 
477                         my $cp_up = $session->request(
478                                 "open-ils.storage.direct.asset.copy.update",
479                                 $copy );
480                         $cp_up->gather(1);
481                 
482                         my $ci_up = $session->request(
483                                 "open-ils.storage.direct.action.circulation.update",
484                                 $circ );
485                         $ci_up->gather(1);
486                 
487                         $apputils->commit_db_session($session);
488                 
489                         warn "Checkin succeeded\n";
490                 }
491         
492         } catch Error with {
493                 my $e = shift;
494                 $err = "Error checking in: $e";
495         };
496         
497         if($err) {
498                 return { record => undef, status => -1, text => $err };
499
500         } else {
501
502                 my $record = $apputils->simple_scalar_request(
503                         "open-ils.storage",
504                         "open-ils.storage.fleshed.biblio.record_entry.retrieve_by_copy",
505                         $copy->id() );
506
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} };
512         }
513
514         return 1;
515
516 }
517
518
519
520
521
522 1;