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