]> git.evergreen-ils.org Git - Evergreen.git/blob - Open-ILS/src/perlmods/OpenILS/Application/Circ.pm
added auto-billing for lost items
[Evergreen.git] / Open-ILS / src / perlmods / OpenILS / Application / Circ.pm
1 package OpenILS::Application::Circ;
2 use base qw/OpenSRF::Application/;
3 use strict; use warnings;
4
5 use OpenILS::Application::Circ::Circulate;
6 use OpenILS::Application::Circ::Rules;
7 use OpenILS::Application::Circ::Survey;
8 use OpenILS::Application::Circ::StatCat;
9 use OpenILS::Application::Circ::Holds;
10 use OpenILS::Application::Circ::Money;
11 use OpenILS::Application::Circ::NonCat;
12 use OpenILS::Application::Circ::CopyLocations;
13
14 use DateTime;
15 use DateTime::Format::ISO8601;
16
17 use OpenILS::Application::AppUtils;
18 my $apputils = "OpenILS::Application::AppUtils";
19 my $U = $apputils;
20 use OpenSRF::Utils qw/:datetime/;
21 use OpenILS::Utils::ModsParser;
22 use OpenILS::Event;
23 use OpenSRF::EX qw(:try);
24 use OpenSRF::Utils::Logger qw(:logger);
25 use OpenILS::Utils::Fieldmapper;
26 #my $logger = "OpenSRF::Utils::Logger";
27
28
29 # ------------------------------------------------------------------------
30 # Top level Circ package;
31 # ------------------------------------------------------------------------
32
33 sub initialize {
34         my $self = shift;
35         OpenILS::Application::Circ::Circulate->initialize();
36 }
37
38
39 __PACKAGE__->register_method(
40         method => 'retrieve_circ',
41         api_name        => 'open-ils.circ.retrieve',
42         signature => q/
43                 Retrieve a circ object by id
44                 @param authtoken Login session key
45                 @pararm circid The id of the circ object
46         /
47 );
48 sub retrieve_circ {
49         my( $s, $c, $a, $i ) = @_;
50         my($reqr, $evt) = $U->checksesperm($a, 'VIEW_CIRCULATIONS');
51         return $evt if $evt;
52         my $circ;
53         ($circ, $evt) = $U->fetch_circulation($i);
54         return $evt if $evt;
55         return $circ;
56 }
57
58
59 # ------------------------------------------------------------------------
60 # Returns an array of {circ, record} hashes checked out by the user.
61 # ------------------------------------------------------------------------
62 __PACKAGE__->register_method(
63         method  => "checkouts_by_user",
64         api_name        => "open-ils.circ.actor.user.checked_out",
65         NOTES           => <<"  NOTES");
66         Returns a list of open circulations as a pile of objects.  each object
67         contains the relevant copy, circ, and record
68         NOTES
69
70 sub checkouts_by_user {
71         my( $self, $client, $user_session, $user_id ) = @_;
72
73         my( $requestor, $target, $copy, $record, $evt );
74
75         ( $requestor, $target, $evt ) = 
76                 $apputils->checkses_requestor( $user_session, $user_id, 'VIEW_CIRCULATIONS');
77         return $evt if $evt;
78
79         my $circs = $apputils->simplereq(
80                 'open-ils.storage',
81                 "open-ils.storage.direct.action.open_circulation.search.atomic", 
82                 { usr => $target->id, checkin_time => undef } );
83 #               { usr => $target->id } );
84
85         my @results;
86         for my $circ (@$circs) {
87
88                 ( $copy, $evt )  = $apputils->fetch_copy($circ->target_copy);
89                 return $evt if $evt;
90
91                 $logger->debug("Retrieving record for copy " . $circ->target_copy);
92
93                 ($record, $evt) = $apputils->fetch_record_by_copy( $circ->target_copy );
94                 return $evt if $evt;
95
96                 my $mods = $apputils->record_to_mvr($record);
97
98                 push( @results, { copy => $copy, circ => $circ, record => $mods } );
99         }
100
101         return \@results;
102
103 }
104
105
106
107 __PACKAGE__->register_method(
108         method  => "checkouts_by_user_slim",
109         api_name        => "open-ils.circ.actor.user.checked_out.slim",
110         NOTES           => <<"  NOTES");
111         Returns a list of open circulation objects
112         NOTES
113
114 sub checkouts_by_user_slim {
115         my( $self, $client, $user_session, $user_id ) = @_;
116
117         my( $requestor, $target, $copy, $record, $evt );
118
119         ( $requestor, $target, $evt ) = 
120                 $apputils->checkses_requestor( $user_session, $user_id, 'VIEW_CIRCULATIONS');
121         return $evt if $evt;
122
123         $logger->debug( 'User ' . $requestor->id . 
124                 " retrieving checked out items for user " . $target->id );
125
126         # XXX Make the call correct..
127         return $apputils->simplereq(
128                 'open-ils.storage',
129                 "open-ils.storage.direct.action.open_circulation.search.atomic", 
130                 { usr => $target->id, checkin_time => undef } );
131 #               { usr => $target->id } );
132 }
133
134
135
136
137 __PACKAGE__->register_method(
138         method  => "title_from_transaction",
139         api_name        => "open-ils.circ.circ_transaction.find_title",
140         NOTES           => <<"  NOTES");
141         Returns a mods object for the title that is linked to from the 
142         copy from the hold that created the given transaction
143         NOTES
144
145 sub title_from_transaction {
146         my( $self, $client, $login_session, $transactionid ) = @_;
147
148         my( $user, $circ, $title, $evt );
149
150         ( $user, $evt ) = $apputils->checkses( $login_session );
151         return $evt if $evt;
152
153         ( $circ, $evt ) = $apputils->fetch_circulation($transactionid);
154         return $evt if $evt;
155         
156         ($title, $evt) = $apputils->fetch_record_by_copy($circ->target_copy);
157         return $evt if $evt;
158
159         return $apputils->record_to_mvr($title);
160 }
161
162
163 __PACKAGE__->register_method(
164         method  => "set_circ_lost",
165         api_name        => "open-ils.circ.circulation.set_lost",
166         NOTES           => <<"  NOTES");
167         Params are login, barcode
168         login must have SET_CIRC_LOST perms
169         Sets a circulation to lost
170         NOTES
171
172 __PACKAGE__->register_method(
173         method  => "set_circ_lost",
174         api_name        => "open-ils.circ.circulation.set_claims_returned",
175         NOTES           => <<"  NOTES");
176         Params are login, barcode
177         login must have SET_CIRC_MISSING perms
178         Sets a circulation to lost
179         NOTES
180
181 sub set_circ_lost {
182         my( $self, $client, $login, $args ) = @_;
183         my( $user, $circ, $copy, $evt );
184
185         my $barcode             = $$args{barcode};
186         my $backdate    = $$args{backdate};
187
188         ( $user, $evt ) = $U->checkses($login);
189         return $evt if $evt;
190
191         # Grab the related copy
192         ($copy, $evt) = $U->fetch_copy_by_barcode($barcode);
193         return $evt if $evt;
194
195         my $isclaims    = $self->api_name =~ /claims_returned/;
196         my $islost              = $self->api_name =~ /lost/;
197         my $session             = $U->start_db_session(); 
198
199         # grab the circulation
200         ( $circ ) = $U->fetch_open_circulation( $copy->id );
201         return 1 unless $circ;
202
203         if($islost) {
204                 $evt  = _set_circ_lost($copy, $circ, $user, $session) if $islost;
205                 return $evt if $evt;
206         }
207
208         if($isclaims) {
209
210                 $evt = $U->check_perms($user->id, $circ->circ_lib, 'SET_CIRC_CLAIMS_RETURNED');
211                 return $evt if $evt;
212                 $circ->stop_fines("CLAIMSRETURNED");
213
214                 $logger->activity("user ".$user->id." marking circ".  $circ->id. " as claims returned");
215
216                 # allow the caller to backdate the circulation and void any fines
217                 # that occurred after the backdate
218                 if($backdate) {
219                         OpenILS::Application::Circ::Circulate::_checkin_handle_backdate(
220                                 $backdate, $circ, $user, $session );
221                 }
222         }
223
224         my $s = $session->request(
225                 "open-ils.storage.direct.action.circulation.update", $circ )->gather(1);
226
227         return $U->DB_UPDATE_FAILED($circ) unless defined($s);
228         $U->commit_db_session($session);
229
230         return 1;
231 }
232
233 sub _set_circ_lost {
234         my( $copy, $circ, $reqr, $session ) = @_;
235
236         my $evt = $U->check_perms($reqr->id, $circ->circ_lib, 'SET_CIRC_LOST');
237         return $evt if $evt;
238
239         $logger->activity("user ".$reqr->id." marking copy ".$copy->id.
240                 " lost  for circ ".  $circ->id. " and checking for necessary charges");
241
242         my $newstat = $U->copy_status_from_name('lost');
243         if( $copy->status ne $newstat->id ) {
244
245                 $copy->status($newstat);
246                 $U->update_copy(
247                         copy            => $copy, 
248                         editor  => $reqr->id, 
249                         session => $session);
250         }
251
252         # if the copy has a price defined and/or a processing fee, bill the patron
253         my $amount = $copy->price || 0;
254         my $owner = $U->fetch_copy_owner($copy->id);
255         $logger->info("circ fetching org settings for $owner to determine processing fee");
256         my $settings = $U->simplereq(
257                 'open-ils.actor',
258                 'open-ils.actor.org_unit.settings.retrieve', $owner );
259         my $f = $settings->{'circ.processing_fee'} || 0;
260         $amount += $f;
261         
262         if( $amount > 0 ) {
263
264                 $logger->activity("The system is charging $amount ".
265                         "for lost materials on circulation ".$circ->id);
266
267                 my $bill = Fieldmapper::money::billing->new;
268
269                 $bill->xact( $circ->id );
270                 $bill->amount( $amount );
271                 $bill->billing_type('Lost materials'); # - these strings should be configurable some day
272                 $bill->note('SYSTEM GENERATED');
273
274                 my $id = $session->request(
275                         'open-ils.storage.direct.money.billing.create', $bill )->gather(1);
276
277                 return $U->DB_UPDATE_FAILED($bill) unless defined $id;
278         }
279
280         $circ->stop_fines("LOST");              
281         return undef;
282 }
283
284
285 __PACKAGE__->register_method (
286         method          => 'set_circ_due_date',
287         api_name                => 'open-ils.circ.circulation.due_date.update',
288         signature       => q/
289                 Updates the due_date on the given circ
290                 @param authtoken
291                 @param circid The id of the circ to update
292                 @param date The timestamp of the new due date
293         /
294 );
295
296 sub set_circ_due_date {
297         my( $s, $c, $authtoken, $circid, $date ) = @_;
298         my ($circ, $evt) = $U->fetch_circulation($circid);
299         return $evt if $evt;
300
301         my $reqr;
302         ($reqr, $evt) = $U->checkses($authtoken);
303         return $evt if $evt;
304
305         $evt = $U->check_perms($reqr->id, $circ->circ_lib, 'CIRC_OVERRIDE_DUE_DATE');
306         return $evt if $evt;
307
308         $date = clense_ISO8601($date);
309         $logger->activity("user ".$reqr->id.
310                 " updating due_date on circ $circid: $date");
311
312         $circ->due_date($date);
313         my $stat = $U->storagereq(
314                 'open-ils.storage.direct.action.circulation.update', $circ);
315         return $U->DB_UPDATE_FAILED unless defined $stat;
316         return $stat;
317 }
318
319
320 __PACKAGE__->register_method(
321         method          => "create_in_house_use",
322         api_name                => 'open-ils.circ.in_house_use.create',
323         signature       =>      q/
324                 Creates an in-house use action.
325                 @param $authtoken The login session key
326                 @param params A hash of params including
327                         'location' The org unit id where the in-house use occurs
328                         'copyid' The copy in question
329                         'count' The number of in-house uses to apply to this copy
330                 @return An array of id's representing the id's of the newly created
331                 in-house use objects or an event on an error
332         /);
333
334 sub create_in_house_use {
335         my( $self, $client, $authtoken, $params ) = @_;
336
337         my( $staff, $evt, $copy );
338         my $org                 = $params->{location};
339         my $copyid              = $params->{copyid};
340         my $count               = $params->{count} || 1;
341         my $use_time    = $params->{use_time} || 'now';
342
343         if(!$copyid) {
344                 my $barcode = $params->{barcode};
345                 ($copy, $evt) = $U->fetch_copy_by_barcode($barcode);
346                 return $evt if $evt;
347                 $copyid = $copy->id;
348         }
349
350         ($staff, $evt) = $U->checkses($authtoken);
351         return $evt if $evt;
352
353         ($copy, $evt) = $U->fetch_copy($copyid) unless $copy;
354         return $evt if $evt;
355
356         $evt = $U->check_perms($staff->id, $org, 'CREATE_IN_HOUSE_USE');
357         return $evt if $evt;
358
359         $logger->activity("User " . $staff->id .
360                 " creating $count in-house use(s) for copy $copyid at location $org");
361
362         if( $use_time ne 'now' ) {
363                 $use_time = clense_ISO8601($use_time);
364                 $logger->debug("in_house_use setting use time to $use_time");
365         }
366
367         my @ids;
368         for(1..$count) {
369                 my $ihu = Fieldmapper::action::in_house_use->new;
370
371                 $ihu->item($copyid);
372                 $ihu->staff($staff->id);
373                 $ihu->org_unit($org);
374                 $ihu->use_time($use_time);
375
376                 my $id = $U->simplereq(
377                         'open-ils.storage',
378                         'open-ils.storage.direct.action.in_house_use.create', $ihu );
379
380                 return $U->DB_UPDATE_FAILED($ihu) unless $id;
381                 push @ids, $id;
382         }
383
384         return \@ids;
385 }
386
387
388
389 __PACKAGE__->register_method(
390         method  => "view_circ_patrons",
391         api_name        => "open-ils.circ.copy_checkout_history.retrieve",
392         notes           => q/
393                 Retrieves the last X users who checked out a given copy
394                 @param authtoken The login session key
395                 @param copyid The copy to check
396                 @param count How far to go back in the item history
397                 @return An array of patron ids
398         /);
399
400 sub view_circ_patrons {
401         my( $self, $client, $authtoken, $copyid, $count ) = @_; 
402
403         my( $requestor, $evt ) = $U->checksesperm(
404                         $authtoken, 'VIEW_COPY_CHECKOUT_HISTORY' );
405         return $evt if $evt;
406
407         return [] unless $count;
408
409         my $circs = $U->storagereq(
410                 'open-ils.storage.direct.action.circulation.search_where.atomic',
411                         { 
412                                 target_copy => $copyid, 
413                                 opac_renewal => 'f',   
414                                 desk_renewal => 'f',
415                                 phone_renewal => 'f',
416                         }, 
417                         { 
418                                 limit => $count, 
419                                 order_by => "xact_start DESC" 
420                         } );
421
422
423         my @users;
424         push(@users, $_->usr) for @$circs;
425         return \@users;
426 }
427
428
429
430 __PACKAGE__->register_method(
431         method          => 'fetch_notes',
432         api_name                => 'open-ils.circ.copy_note.retrieve.all',
433         signature       => q/
434                 Returns an array of copy note objects.  
435                 @param args A named hash of parameters including:
436                         authtoken       : Required if viewing non-public notes
437                         itemid          : The id of the item whose notes we want to retrieve
438                         pub                     : True if all the caller wants are public notes
439                 @return An array of note objects
440         /);
441
442 __PACKAGE__->register_method(
443         method          => 'fetch_notes',
444         api_name                => 'open-ils.circ.call_number_note.retrieve.all',
445         signature       => q/@see open-ils.circ.copy_note.retrieve.all/);
446
447 __PACKAGE__->register_method(
448         method          => 'fetch_notes',
449         api_name                => 'open-ils.circ.title_note.retrieve.all',
450         signature       => q/@see open-ils.circ.copy_note.retrieve.all/);
451
452
453 # NOTE: VIEW_COPY/VOLUME/TITLE_NOTES perms should always be global
454 sub fetch_notes {
455         my( $self, $connection, $args ) = @_;
456
457         my $id = $$args{itemid};
458         my $authtoken = $$args{authtoken};
459         my( $r, $evt);
460
461         if( $self->api_name =~ /copy/ ) {
462                 if( $$args{pub} ) {
463                         return $U->storagereq(
464                                 'open-ils.storage.direct.asset.copy_note.search_where.atomic',
465                                 { owning_copy => $id, pub => 't' } );
466                 } else {
467                         ( $r, $evt ) = $U->checksesperm($authtoken, 'VIEW_COPY_NOTES');
468                         return $evt if $evt;
469                         return $U->storagereq(
470                                 'open-ils.storage.direct.asset.copy_note.search.owning_copy.atomic', $id );
471                 }
472
473         } elsif( $self->api_name =~ /call_number/ ) {
474                 if( $$args{pub} ) {
475                         return $U->storagereq(
476                                 'open-ils.storage.direct.asset.call_number_note.search_where.atomic',
477                                 { call_number => $id, pub => 't' } );
478                 } else {
479                         ( $r, $evt ) = $U->checksesperm($authtoken, 'VIEW_VOLUME_NOTES');
480                         return $evt if $evt;
481                         return $U->storagereq(
482                                 'open-ils.storage.direct.asset.call_number_note.search.call_number.atomic', $id );
483                 }
484
485         } elsif( $self->api_name =~ /title/ ) {
486                 if( $$args{pub} ) {
487                         return $U->storagereq(
488                                 'open-ils.storage.direct.bilbio.record_note.search_where.atomic',
489                                 { record => $id, pub => 't' } );
490                 } else {
491                         ( $r, $evt ) = $U->checksesperm($authtoken, 'VIEW_TITLE_NOTES');
492                         return $evt if $evt;
493                         return $U->storagereq(
494                                 'open-ils.storage.direct.asset.call_number_note.search.call_number.atomic', $id );
495                 }
496         }
497
498         return undef;
499 }
500
501 __PACKAGE__->register_method(
502         method          => 'create_copy_note',
503         api_name                => 'open-ils.circ.copy_note.create',
504         signature       => q/
505                 Creates a new copy note
506                 @param authtoken The login session key
507                 @param note     The note object to create
508                 @return The id of the new note object
509         /);
510
511 sub create_copy_note {
512         my( $self, $connection, $authtoken, $note ) = @_;
513         my( $cnowner, $requestor, $evt );
514
515         ($cnowner, $evt) = $U->fetch_copy_owner($note->owning_copy);
516         return $evt if $evt;
517         ($requestor, $evt) = $U->checkses($authtoken);
518         return $evt if $evt;
519         $evt = $U->check_perms($requestor->id, $cnowner, 'CREATE_COPY_NOTE');
520         return $evt if $evt;
521
522         $note->create_date('now');
523         $note->creator($requestor->id);
524         $note->pub( ($note->pub) ? 't' : 'f' );
525
526         my $id = $U->storagereq(
527                 'open-ils.storage.direct.asset.copy_note.create', $note );
528         return $U->DB_UPDATE_FAILED($note) unless $id;
529
530         $logger->activity("User ".$requestor->id." created a new copy ".
531                 "note [$id] for copy ".$note->owning_copy." with text ".$note->value);
532
533         return $id;
534 }
535
536 __PACKAGE__->register_method(
537         method          => 'delete_copy_note',
538         api_name                =>      'open-ils.circ.copy_note.delete',
539         signature       => q/
540                 Deletes an existing copy note
541                 @param authtoken The login session key
542                 @param noteid The id of the note to delete
543                 @return 1 on success - Event otherwise.
544                 /);
545
546 sub delete_copy_note {
547         my( $self, $conn, $authtoken, $noteid ) = @_;
548         my( $requestor, $note, $owner, $evt );
549
550         ($requestor, $evt) = $U->checkses($authtoken);
551         return $evt if $evt;
552
553         ($note, $evt) = $U->fetch_copy_note($noteid);
554         return $evt if $evt;
555
556         if( $note->creator ne $requestor->id ) {
557                 ($owner, $evt) = $U->fetch_copy_onwer($note->owning_copy);
558                 return $evt if $evt;
559                 $evt = $U->check_perms($requestor->id, $owner, 'DELETE_COPY_NOTE');
560                 return $evt if $evt;
561         }
562
563         my $stat = $U->storagereq(
564                 'open-ils.storage.direct.asset.copy_note.delete', $noteid );
565         return $U->DB_UPDATE_FAILED($noteid) unless $stat;
566
567         $logger->activity("User ".$requestor->id." deleted copy note $noteid");
568         return 1;
569 }
570
571 =head this method is really inefficient - get rid of me
572
573 __PACKAGE__->register_method(
574         method          => 'note_batch',
575         api_name                => 'open-ils.circ.biblio_notes.public.batch.retrieve',
576         signature       => q/
577                 Returns a set of notes for a given set of titles, volumes, and copies.
578                 @param titleid The id of the title who's notes are retrieving
579                 @return A list like so:
580                         {
581                                 "titles"                : [ { id : $id, notes : [ n1, n2 ] },... ]
582                                 "volumes"       : [ { id : $id, notes : [ n1, n2 ] },... ]
583                                 "copies"                : [ { id : $id, notes : [ n1, n2 ] },... ]
584                         }
585         /
586 );
587
588 sub note_batch {
589         my( $self, $conn, $titleid ) = @_;
590
591         my @copies;
592         my $cns = $U->storagereq(
593                 'open-ils.storage.id_list.asset.call_number.search_where.atomic', 
594                 { record => $titleid, deleted => 'f' } );
595                 #'open-ils.storage.id_list.asset.call_number.search.record.atomic', $titleid );
596
597         for my $c (@$cns) {
598                 my $copyids = $U->storagereq(
599                         #'open-ils.storage.id_list.asset.copy.search.call_number.atomic', $c);
600                         'open-ils.storage.id_list.asset.copy.search_where.atomic', { call_number => $c, deleted => 'f' });
601                 push(@copies, @$copyids);
602         }
603
604         return _note_batch( { titles => [$titleid], volumes => $cns, copies => \@copies} );
605 }
606
607
608 sub _note_batch {
609         my $args = shift;
610
611         my %resp;
612         $resp{titles}   = [];
613         $resp{volumes} = [];
614         $resp{copies}   = [];
615
616         my $titles      = (ref($$args{titles})) ? $$args{titles} : [];
617         my $volumes = (ref($$args{volumes})) ? $$args{volumes} : [];
618         my $copies      = (ref($$args{copies})) ? $$args{copies} : [];
619
620         for my $title (@$titles) {
621                 my $notes = $U->storagereq(
622                         'open-ils.storage.direct.biblio.record_note.search_where.atomic', 
623                         { record => $title, pub => 't' });
624                 push(@{$resp{titles}}, {id => $title, notes => $notes}) if @$notes;
625         }
626
627         for my $volume (@$volumes) {
628                 my $notes = $U->storagereq(
629                         'open-ils.storage.direct.asset.call_number_note.search_where.atomic',
630                         { call_number => $volume, pub => 't' });
631                 push( @{$resp{volumes}}, {id => $volume, notes => $notes} ) if @$notes;
632         }
633
634
635         for my $copy (@$copies) {
636                 $logger->debug("Fetching copy notes for copy $copy");
637                 my $notes = $U->storagereq(
638                         'open-ils.storage.direct.asset.copy_note.search_where.atomic',
639                         { owning_copy => $copy, pub => 't' });
640                 push( @{$resp{copies}}, { id => $copy, notes => $notes }) if @$notes;
641         }
642
643         return \%resp;
644 }
645
646 =cut
647
648
649
650
651
652
653
654 1;