]> git.evergreen-ils.org Git - Evergreen.git/blob - Open-ILS/src/perlmods/OpenILS/Application/Circ/Rules.pm
Let the onslaught continue...
[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 use constant NO_COPY => 100;
48
49 sub initialize {
50         my $self = shift;
51         my $conf = OpenSRF::Utils::SettingsClient->new;
52
53         # ----------------------------------------------------------------
54         # set up the rules scripts
55         # ----------------------------------------------------------------
56         $circ_script = $conf->config_value(                                     
57                 "apps", "open-ils.circ","app_settings", "rules", "main");
58
59         $permission_script = $conf->config_value(                       
60                 "apps", "open-ils.circ","app_settings", "rules", "permission");
61
62         $duration_script = $conf->config_value(                 
63                 "apps", "open-ils.circ","app_settings", "rules", "duration");
64
65         $recurring_fines_script = $conf->config_value(  
66                 "apps", "open-ils.circ","app_settings", "rules", "recurring_fines");
67
68         $max_fines_script = $conf->config_value(                        
69                 "apps", "open-ils.circ","app_settings", "rules", "max_fines");
70
71
72         $cache_handle = OpenSRF::Utils::Cache->new();
73 }
74
75
76
77 # ----------------------------------------------------------------
78 # Collect all of the objects necessary for calculating the
79 # circ matrix.
80 # ----------------------------------------------------------------
81 sub gather_circ_objects {
82         my( $session, $barcode_string, $patron_id ) = @_;
83
84         throw OpenSRF::EX::ERROR 
85                 ("gather_circ_objects needs data")
86                         unless ($barcode_string and $patron_id);
87
88         warn "Gathering circ objects with barcode $barcode_string and patron id $patron_id\n";
89
90         
91
92         # see if all of the circ objects are in cache
93         my $cache_key =  "circ_object_" . md5_hex( $barcode_string, $patron_id );
94         $circ_objects = $cache_handle->get_cache($cache_key);
95
96         if($circ_objects) { 
97                 $stash = Template::Stash->new(
98                         circ_objects                    => $circ_objects,
99                         result                                  => [],
100                         target_copy_status      => 1,
101                         );
102                 return;
103         }
104
105         # grab the patron standing list of we don't already have it
106         # only necessary if the circ objects have not been built yet
107         if(!$patron_standings) {
108                 my $standing_req = $session->request(
109                         "open-ils.storage.direct.config.standing.retrieve.all.atomic");
110                 $patron_standings = $standing_req->gather(1);
111                 $patron_standings =
112                         { map { (''.$_->id => $_->value) } @$patron_standings };
113         }
114
115         # grab patron profiles
116         if(!$patron_profiles) {
117                 my $profile_req = $session->request(
118                         "open-ils.storage.direct.actor.profile.retrieve.all.atomic");
119                 $patron_profiles = $profile_req->gather(1);
120                 $patron_profiles =
121                         { map { (''.$_->id => $_->name) } @$patron_profiles };
122         }
123
124
125         my $copy_req    = $session->request(
126                 "open-ils.storage.fleshed.asset.copy.search.barcode", 
127                 $barcode_string );
128
129         my $patron_req  = $session->request(
130                 "open-ils.storage.direct.actor.user.retrieve", 
131                 $patron_id );
132
133         my $copy = $copy_req->gather(1)->[0];
134         if(!$copy) { return NO_COPY; }
135
136         $copy->status( $copy->status->name );
137         $circ_objects->{copy} = $copy;
138
139         my $patron = $patron_req->gather(1);
140         $patron->standing($patron_standings->{$patron->standing()});
141         $patron->profile( $patron_profiles->{$patron->profile});
142         $circ_objects->{patron} = $patron;
143
144         
145         my $title_req   = $session->request(
146                 "open-ils.storage.fleshed.biblio.record_entry.retrieve_by_copy",
147                 $circ_objects->{copy}->id() );
148
149         $circ_objects->{title} = $title_req->gather(1);
150
151         $cache_handle->put_cache( $cache_key, $circ_objects, 30 );
152
153         $stash = Template::Stash->new(
154                         circ_objects                    => $circ_objects,
155                         result                                  => [],
156                         target_copy_status      => 1,
157                         );
158
159 }
160
161
162
163 sub run_script {
164
165         my $result;
166
167         my $template = Template->new(
168                 { 
169                         STASH                   => $stash,
170                         ABSOLUTE                => 1, 
171                         OUTPUT          => \$result,
172                 }
173         );
174
175         my $status = $template->process($circ_script);
176
177         if(!$status) { 
178                 throw OpenSRF::EX::ERROR 
179                         ("Error processing circ script " .  $template->error()); 
180         }
181
182         warn "Script result: $result\n";
183 }
184
185
186
187
188 __PACKAGE__->register_method(
189         method  => "permit_circ",
190         api_name        => "open-ils.circ.permit_checkout",
191 );
192
193 sub permit_circ {
194         my( $self, $client, $user_session, $barcode, $user_id, $outstanding_count ) = @_;
195
196         $outstanding_count ||= 0;
197
198         my $session     = OpenSRF::AppSession->create("open-ils.storage");
199         
200         # collect items necessary for circ calculation
201         my $status = gather_circ_objects( $session, $barcode, $user_id );
202
203         if( $status == NO_COPY ) {
204                 return { record => undef, 
205                         status => NO_COPY, 
206                         text => "No copy available with barcode $barcode"
207                 };
208         }
209
210
211         
212         $stash->set("run_block", $permission_script);
213
214         # grab the number of copies checked out by the patron as
215         # well as the total fines
216         my $summary_req = $session->request(
217                 "open-ils.storage.action.circulation.patron_summary",
218                 $stash->get("circ_objects")->{patron}->id );
219         my $summary = $summary_req->gather(1);
220
221         $stash->set("patron_copies", $summary->[0]  + $outstanding_count );
222         $stash->set("patron_fines", $summary->[1] );
223
224         # run the permissibility script
225         run_script();
226         my $obj = $stash->get("circ_objects");
227
228         # turn the biblio record into a friendly object
229         my $u = OpenILS::Utils::ModsParser->new();
230         $u->start_mods_batch( $obj->{title}->marc() );
231         my $mods = $u->finish_mods_batch();
232
233         my $arr = $stash->get("result");
234         return { record => $mods, status => $arr->[0], text => $arr->[1] };
235
236 }
237
238
239 __PACKAGE__->register_method(
240         method  => "circulate",
241         api_name        => "open-ils.circ.checkout.barcode",
242 );
243
244 sub circulate {
245         my( $self, $client, $user_session, $barcode, $patron ) = @_;
246
247
248         my $session = $apputils->start_db_session();
249
250         gather_circ_objects( $session, $barcode, $patron );
251
252         # grab the copy statuses if we don't already have them
253         if(!$copy_statuses) {
254                 my $csreq = $session->request(
255                         "open-ils.storage.direct.config.copy_status.retrieve.all.atomic" );
256                 $copy_statuses = $csreq->gather(1);
257         }
258
259         # put copy statuses into the stash
260         $stash->set("copy_statuses", $copy_statuses );
261
262         my $copy = $circ_objects->{copy};
263         my ($circ, $duration, $recurring, $max) =  run_circ_scripts($session);
264
265         # commit new circ object to db
266         my $commit = $session->request(
267                 "open-ils.storage.direct.action.circulation.create",
268                 $circ );
269         my $id = $commit->gather(1);
270
271         if(!$id) {
272                 throw OpenSRF::EX::ERROR 
273                         ("Error creating new circulation object");
274         }
275
276         # update the copy with the new circ
277         $copy->status( $stash->get("target_copy_status") );
278         $copy->location( $copy->location->id );
279         $copy->circ_lib( $copy->circ_lib->id );
280
281         # commit copy to db
282         my $copy_update = $session->request(
283                 "open-ils.storage.direct.asset.copy.update",
284                 $copy );
285         $copy_update->gather(1);
286
287         $apputils->commit_db_session($session);
288
289         # remove our circ object from the cache
290         $cache_handle->delete_cache("circ_object_" . md5_hex($barcode, $patron));
291
292         # re-retrieve the the committed circ object  
293         $circ = $apputils->simple_scalar_request(
294                 "open-ils.storage",
295                 "open-ils.storage.direct.action.circulation.retrieve",
296                 $id );
297
298
299         # push the rules and due date into the circ object
300         $circ->duration_rule($duration);
301         $circ->max_fine_rule($max);
302         $circ->recuring_fine_rule($recurring);
303
304         my $due_date = 
305                 OpenSRF::Utils->interval_to_seconds( 
306                         $circ->duration ) + int(time());
307
308         $circ->due_date($due_date);
309
310         return $circ;
311
312 }
313
314
315
316 # runs the duration, recurring_fines, and max_fines scripts.
317 # builds the new circ object based on the rules returned from 
318 # these scripts. 
319 # returns (circ, duration_rule, recurring_fines_rule, max_fines_rule)
320 sub run_circ_scripts {
321         my $session = shift;
322
323         # go through all of the scripts and process
324         # each script returns 
325         # [ rule_name, level (appropriate to the script) ]
326         $stash->set("result", [] );
327         $stash->set("run_block", $duration_script);
328         run_script();
329         my $duration_rule = $stash->get("result");
330
331         $stash->set("run_block", $recurring_fines_script);
332         $stash->set("result", [] );
333         run_script();
334         my $rec_fines_rule = $stash->get("result");
335
336         $stash->set("run_block", $max_fines_script);
337         $stash->set("result", [] );
338         run_script();
339         my $max_fines_rule = $stash->get("result");
340
341         my $obj = $stash->get("circ_objects");
342
343         # ----------------------------------------------------------
344         # find the rules objects based on the rule names returned from
345         # the various scripts.
346         my $dur_req = $session->request(
347                 "open-ils.storage.direct.config.rules.circ_duration.search.name",
348                 $duration_rule->[0] );
349
350         my $rec_req = $session->request(
351                 "open-ils.storage.direct.config.rules.recuring_fine.search.name",
352                 $rec_fines_rule->[0] );
353
354         my $max_req = $session->request(
355                 "open-ils.storage.direct.config.rules.max_fine.search.name",
356                 $max_fines_rule->[0] );
357
358         my $duration    = $dur_req->gather(1)->[0];
359         my $recurring   = $rec_req->gather(1)->[0];
360         my $max                 = $max_req->gather(1)->[0];
361
362         my $copy = $circ_objects->{copy};
363
364         # build the new circ object
365         my $circ =  build_circ_object($session, $copy, $duration_rule->[1], 
366                         $rec_fines_rule->[1], $duration, $recurring, $max );
367
368         return ($circ, $duration, $recurring, $max);
369
370 }
371
372 # ------------------------------------------------------------------
373 # Builds a new circ object
374 # ------------------------------------------------------------------
375 sub build_circ_object {
376         my( $session, $copy, $dur_level, $rec_level, 
377                         $duration, $recurring, $max ) = @_;
378
379         my $circ = new Fieldmapper::action::circulation;
380
381         $circ->circ_lib( $copy->circ_lib->id() );
382         if($dur_level == 1) {
383                 $circ->duration( $duration->shrt );
384         } elsif($dur_level == 2) {
385                 $circ->duration( $duration->normal );
386         } elsif($dur_level == 3) {
387                 $circ->duration( $duration->extended );
388         }
389
390         if($rec_level == 1) {
391                 $circ->recuring_fine( $recurring->low );
392         } elsif($rec_level == 2) {
393                 $circ->recuring_fine( $recurring->normal );
394         } elsif($rec_level == 3) {
395                 $circ->recuring_fine( $recurring->high );
396         }
397
398         $circ->duration_rule( $duration->name );
399         $circ->recuring_fine_rule( $recurring->name );
400         $circ->max_fine_rule( $max->name );
401         $circ->max_fine( $max->amount );
402
403         $circ->fine_interval($recurring->recurance_interval);
404         $circ->renewal_remaining( $duration->max_renewals );
405         $circ->target_copy( $copy->id );
406         $circ->usr( $circ_objects->{patron}->id );
407
408         return $circ;
409
410 }
411
412 __PACKAGE__->register_method(
413         method  => "checkin",
414         api_name        => "open-ils.circ.checkin.barcode",
415 );
416
417 sub checkin {
418         my( $self, $user_session, $client, $barcode ) = @_;
419
420         my $err;
421         my $copy;
422
423         try {
424                 my $session = $apputils->start_db_session();
425         
426                 warn "retrieving copy for checkin\n";
427
428                 if(!$shelving_locations) {
429                         my $sh_req = $session->request(
430                                 "open-ils.storage.direct.asset.copy_location.retrieve.all.atomic");
431                         $shelving_locations = $sh_req->gather(1);
432                         $shelving_locations = 
433                                 { map { (''.$_->id => $_->name) } @$shelving_locations };
434                 }
435         
436                 my $copy_req = $session->request(
437                         "open-ils.storage.direct.asset.copy.search.barcode", 
438                         $barcode );
439                 $copy = $copy_req->gather(1)->[0];
440                 $copy->status(0);
441         
442                 # find circ's where the transaction is still open for the
443                 # given copy.  should only be one.
444                 warn "Retrieving circ for checking\n";
445                 my $circ_req = $session->request(
446                         "open-ils.storage.direct.action.circulation.search.atomic",
447                         { target_copy => $copy->id, xact_finish => undef } );
448         
449                 my $circ = $circ_req->gather(1)->[0];
450         
451                 if(!$circ) {
452                         $err = "No circulation exists for the given barcode";
453
454                 } else {
455         
456                         warn "Checking in circ ". $circ->id . "\n";
457                 
458                         $circ->stop_fines("CHECKIN");
459                         $circ->xact_finish("now");
460                 
461                         my $cp_up = $session->request(
462                                 "open-ils.storage.direct.asset.copy.update",
463                                 $copy );
464                         $cp_up->gather(1);
465                 
466                         my $ci_up = $session->request(
467                                 "open-ils.storage.direct.action.circulation.update",
468                                 $circ );
469                         $ci_up->gather(1);
470                 
471                         $apputils->commit_db_session($session);
472                 
473                         warn "Checkin succeeded\n";
474                 }
475         
476         } catch Error with {
477                 my $e = shift;
478                 $err = "Error checking in: $e";
479         };
480         
481         if($err) {
482                 return { record => undef, status => -1, text => $err };
483
484         } else {
485
486                 my $record = $apputils->simple_scalar_request(
487                         "open-ils.storage",
488                         "open-ils.storage.fleshed.biblio.record_entry.retrieve_by_copy",
489                         $copy->id() );
490
491                 my $u = OpenILS::Utils::ModsParser->new();
492                 $u->start_mods_batch( $record->marc() );
493                 my $mods = $u->finish_mods_batch();
494                 return { record => $mods, status => 0, text => "OK", 
495                         route_to => $shelving_locations->{$copy->location} };
496         }
497
498         return 1;
499
500 }
501
502
503
504
505
506 1;