]> git.evergreen-ils.org Git - Evergreen.git/blob - Open-ILS/src/perlmods/OpenILS/Application/Circ/Rules.pm
updated storage 'search' methods to be atomic
[Evergreen.git] / Open-ILS / src / perlmods / OpenILS / Application / Circ / Rules.pm
1 # ---------------------------------------------------------------
2 # Copyright (C) 2005  Georgia Public Library Service 
3 # Bill Erickson <highfalutin@gmail.com>
4
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.
9
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 # ---------------------------------------------------------------
15
16 package OpenILS::Application::Circ::Rules;
17 use base qw/OpenSRF::Application/;
18 use strict; use warnings;
19
20 use OpenSRF::Utils::SettingsClient;
21 use OpenILS::Utils::Fieldmapper;
22 use OpenILS::EX;
23
24 use Template qw(:template);
25 use Template::Stash; 
26
27 use Time::HiRes qw(time);
28 use OpenILS::Utils::ModsParser;
29
30 use OpenSRF::Utils;
31 use OpenSRF::EX qw(:try);
32
33 use OpenILS::Application::AppUtils;
34 my $apputils = "OpenILS::Application::AppUtils";
35 use Digest::MD5 qw(md5_hex);
36
37 # ----------------------------------------------------------------
38 # rules scripts
39 my $circ_script;
40 my $permission_script;
41 my $duration_script;
42 my $recurring_fines_script;
43 my $max_fines_script;
44 # ----------------------------------------------------------------
45
46
47 # data used for this circulation transaction
48 my $circ_objects = {};
49
50 # some static data from the database
51 my $copy_statuses;
52 my $patron_standings;
53 my $patron_profiles;
54 my $shelving_locations;
55
56 # template stash
57 my $stash;
58
59 # memcache for caching the circ objects
60 my $cache_handle;
61
62
63 use constant NO_COPY => 100;
64
65 sub initialize {
66         my $self = shift;
67         my $conf = OpenSRF::Utils::SettingsClient->new;
68
69         # ----------------------------------------------------------------
70         # set up the rules scripts
71         # ----------------------------------------------------------------
72         $circ_script = $conf->config_value(                                     
73                 "apps", "open-ils.circ","app_settings", "rules", "main");
74
75         $permission_script = $conf->config_value(                       
76                 "apps", "open-ils.circ","app_settings", "rules", "permission");
77
78         $duration_script = $conf->config_value(                 
79                 "apps", "open-ils.circ","app_settings", "rules", "duration");
80
81         $recurring_fines_script = $conf->config_value(  
82                 "apps", "open-ils.circ","app_settings", "rules", "recurring_fines");
83
84         $max_fines_script = $conf->config_value(                        
85                 "apps", "open-ils.circ","app_settings", "rules", "max_fines");
86
87
88         $cache_handle = OpenSRF::Utils::Cache->new();
89 }
90
91
92
93 # ----------------------------------------------------------------
94 # Collect all of the objects necessary for calculating the
95 # circ matrix.
96 # ----------------------------------------------------------------
97 sub gather_circ_objects {
98         my( $session, $barcode_string, $patron_id ) = @_;
99
100         throw OpenSRF::EX::ERROR 
101                 ("gather_circ_objects needs data")
102                         unless ($barcode_string and $patron_id);
103
104         warn "Gathering circ objects with barcode $barcode_string and patron id $patron_id\n";
105
106         
107
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);
111
112         if($circ_objects) { 
113                 $stash = Template::Stash->new(
114                         circ_objects                    => $circ_objects,
115                         result                                  => [],
116                         target_copy_status      => 1,
117                         );
118                 return;
119         }
120
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);
127                 $patron_standings =
128                         { map { (''.$_->id => $_->value) } @$patron_standings };
129         }
130
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);
136                 $patron_profiles =
137                         { map { (''.$_->id => $_->name) } @$patron_profiles };
138         }
139
140
141         my $copy_req    = $session->request(
142                 "open-ils.storage.fleshed.asset.copy.search.barcode.atomic", 
143                 $barcode_string );
144
145         my $patron_req  = $session->request(
146                 "open-ils.storage.direct.actor.user.retrieve", 
147                 $patron_id );
148
149         my $copy = $copy_req->gather(1)->[0];
150         if(!$copy) { return NO_COPY; }
151
152         $copy->status( $copy->status->name );
153         $circ_objects->{copy} = $copy;
154
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;
159
160         
161         my $title_req   = $session->request(
162                 "open-ils.storage.fleshed.biblio.record_entry.retrieve_by_copy",
163                 $circ_objects->{copy}->id() );
164
165         $circ_objects->{title} = $title_req->gather(1);
166
167         $cache_handle->put_cache( $cache_key, $circ_objects, 30 );
168
169         $stash = Template::Stash->new(
170                         circ_objects                    => $circ_objects,
171                         result                                  => [],
172                         target_copy_status      => 1,
173                         );
174
175 }
176
177
178
179 sub run_script {
180
181         my $result;
182
183         my $template = Template->new(
184                 { 
185                         STASH                   => $stash,
186                         ABSOLUTE                => 1, 
187                         OUTPUT          => \$result,
188                 }
189         );
190
191         my $status = $template->process($circ_script);
192
193         if(!$status) { 
194                 throw OpenSRF::EX::ERROR 
195                         ("Error processing circ script " .  $template->error()); 
196         }
197
198         warn "Script result: $result\n";
199 }
200
201
202
203
204 __PACKAGE__->register_method(
205         method  => "permit_circ",
206         api_name        => "open-ils.circ.permit_checkout",
207 );
208
209 sub permit_circ {
210         my( $self, $client, $user_session, $barcode, $user_id, $outstanding_count ) = @_;
211
212         $outstanding_count ||= 0;
213
214         my $session     = OpenSRF::AppSession->create("open-ils.storage");
215         
216         # collect items necessary for circ calculation
217         my $status = gather_circ_objects( $session, $barcode, $user_id );
218
219         if( $status == NO_COPY ) {
220                 return { record => undef, 
221                         status => NO_COPY, 
222                         text => "No copy available with barcode $barcode"
223                 };
224         }
225
226
227         
228         $stash->set("run_block", $permission_script);
229
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);
236
237         $stash->set("patron_copies", $summary->[0]  + $outstanding_count );
238         $stash->set("patron_fines", $summary->[1] );
239
240         # run the permissibility script
241         run_script();
242         my $obj = $stash->get("circ_objects");
243
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();
248
249         my $arr = $stash->get("result");
250         return { record => $mods, status => $arr->[0], text => $arr->[1] };
251
252 }
253
254
255 __PACKAGE__->register_method(
256         method  => "circulate",
257         api_name        => "open-ils.circ.checkout.barcode",
258 );
259
260 sub circulate {
261         my( $self, $client, $user_session, $barcode, $patron ) = @_;
262
263
264         my $session = $apputils->start_db_session();
265
266         gather_circ_objects( $session, $barcode, $patron );
267
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);
273         }
274
275         # put copy statuses into the stash
276         $stash->set("copy_statuses", $copy_statuses );
277
278         my $copy = $circ_objects->{copy};
279         my ($circ, $duration, $recurring, $max) =  run_circ_scripts($session);
280
281         # commit new circ object to db
282         my $commit = $session->request(
283                 "open-ils.storage.direct.action.circulation.create",
284                 $circ );
285         my $id = $commit->gather(1);
286
287         if(!$id) {
288                 throw OpenSRF::EX::ERROR 
289                         ("Error creating new circulation object");
290         }
291
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 );
296
297         # commit copy to db
298         my $copy_update = $session->request(
299                 "open-ils.storage.direct.asset.copy.update",
300                 $copy );
301         $copy_update->gather(1);
302
303         $apputils->commit_db_session($session);
304
305         # remove our circ object from the cache
306         $cache_handle->delete_cache("circ_object_" . md5_hex($barcode, $patron));
307
308         # re-retrieve the the committed circ object  
309         $circ = $apputils->simple_scalar_request(
310                 "open-ils.storage",
311                 "open-ils.storage.direct.action.circulation.retrieve",
312                 $id );
313
314
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);
319
320
321 #       my $due_date = 
322 #               OpenSRF::Utils->interval_to_seconds( 
323 #               $circ->duration ) + int(time());
324
325 #       this comes from an earlier setting now
326 #       $circ->due_date($due_date);
327
328         return $circ;
329
330 }
331
332
333
334 # runs the duration, recurring_fines, and max_fines scripts.
335 # builds the new circ object based on the rules returned from 
336 # these scripts. 
337 # returns (circ, duration_rule, recurring_fines_rule, max_fines_rule)
338 sub run_circ_scripts {
339         my $session = shift;
340
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);
346         run_script();
347         my $duration_rule = $stash->get("result");
348
349         $stash->set("run_block", $recurring_fines_script);
350         $stash->set("result", [] );
351         run_script();
352         my $rec_fines_rule = $stash->get("result");
353
354         $stash->set("run_block", $max_fines_script);
355         $stash->set("result", [] );
356         run_script();
357         my $max_fines_rule = $stash->get("result");
358
359         my $obj = $stash->get("circ_objects");
360
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] );
367
368         my $rec_req = $session->request(
369                 "open-ils.storage.direct.config.rules.recuring_fine.search.name.atomic",
370                 $rec_fines_rule->[0] );
371
372         my $max_req = $session->request(
373                 "open-ils.storage.direct.config.rules.max_fine.search.name.atomic",
374                 $max_fines_rule->[0] );
375
376         my $duration    = $dur_req->gather(1)->[0];
377         my $recurring   = $rec_req->gather(1)->[0];
378         my $max                 = $max_req->gather(1)->[0];
379
380         my $copy = $circ_objects->{copy};
381
382         use Data::Dumper;
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);
390
391
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 );
395
396         return ($circ, $duration, $recurring, $max);
397
398 }
399
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 ) = @_;
406
407         my $circ = new Fieldmapper::action::circulation;
408
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 );
416         }
417
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 );
424         }
425
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 );
430
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 );
435
436         return $circ;
437
438 }
439
440 __PACKAGE__->register_method(
441         method  => "checkin",
442         api_name        => "open-ils.circ.checkin.barcode",
443 );
444
445 sub checkin {
446         my( $self, $client, $user_session, $barcode ) = @_;
447
448         my $err;
449         my $copy;
450
451         try {
452                 my $session = $apputils->start_db_session();
453         
454                 warn "retrieving copy for checkin\n";
455
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 };
462                 }
463         
464                 my $copy_req = $session->request(
465                         "open-ils.storage.direct.asset.copy.search.barcode.atomic", 
466                         $barcode );
467                 $copy = $copy_req->gather(1)->[0];
468                 if(!$copy) {
469                         $client->respond_complete(
470                                         OpenILS::EX->new("UNKNOWN_BARCODE")->ex);
471                 }
472
473                 $copy->status(0);
474         
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 } );
481         
482                 my $circ = $circ_req->gather(1)->[0];
483         
484                 if(!$circ) {
485                         $err = "No circulation exists for the given barcode";
486
487                 } else {
488         
489                         warn "Checking in circ ". $circ->id . "\n";
490                 
491                         $circ->stop_fines("CHECKIN");
492                         $circ->xact_finish("now");
493                 
494                         my $cp_up = $session->request(
495                                 "open-ils.storage.direct.asset.copy.update",
496                                 $copy );
497                         $cp_up->gather(1);
498                 
499                         my $ci_up = $session->request(
500                                 "open-ils.storage.direct.action.circulation.update",
501                                 $circ );
502                         $ci_up->gather(1);
503                 
504                         $apputils->commit_db_session($session);
505                 
506                         warn "Checkin succeeded\n";
507                 }
508         
509         } catch Error with {
510                 my $e = shift;
511                 $err = "Error checking in: $e";
512         };
513         
514         if($err) {
515                 return { record => undef, status => -1, text => $err };
516
517         } else {
518
519                 my $record = $apputils->simple_scalar_request(
520                         "open-ils.storage",
521                         "open-ils.storage.fleshed.biblio.record_entry.retrieve_by_copy",
522                         $copy->id() );
523
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} };
529         }
530
531         return 1;
532
533 }
534
535
536
537
538
539 1;