]> git.evergreen-ils.org Git - Evergreen.git/blob - Open-ILS/src/perlmods/OpenILS/Application/Circ.pm
added VIEW_BILLING_TYPE perm awareness
[Evergreen.git] / Open-ILS / src / perlmods / OpenILS / Application / Circ.pm
1 package OpenILS::Application::Circ;
2 use OpenILS::Application;
3 use base qw/OpenILS::Application/;
4 use strict; use warnings;
5
6 use OpenILS::Application::Circ::Circulate;
7 use OpenILS::Application::Circ::Survey;
8 use OpenILS::Application::Circ::StatCat;
9 use OpenILS::Application::Circ::Holds;
10 use OpenILS::Application::Circ::HoldNotify;
11 use OpenILS::Application::Circ::Money;
12 use OpenILS::Application::Circ::NonCat;
13 use OpenILS::Application::Circ::CopyLocations;
14
15 use DateTime;
16 use DateTime::Format::ISO8601;
17
18 use OpenILS::Application::AppUtils;
19
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 use OpenILS::Utils::Editor;
27 use OpenILS::Utils::CStoreEditor q/:funcs/;
28 use OpenILS::Const qw/:const/;
29 use OpenSRF::Utils::SettingsClient;
30
31 my $apputils = "OpenILS::Application::AppUtils";
32 my $U = $apputils;
33
34
35 # ------------------------------------------------------------------------
36 # Top level Circ package;
37 # ------------------------------------------------------------------------
38
39 sub initialize {
40         my $self = shift;
41         OpenILS::Application::Circ::Circulate->initialize();
42 }
43
44
45 __PACKAGE__->register_method(
46         method => 'retrieve_circ',
47         api_name        => 'open-ils.circ.retrieve',
48         signature => q/
49                 Retrieve a circ object by id
50                 @param authtoken Login session key
51                 @pararm circid The id of the circ object
52         /
53 );
54 sub retrieve_circ {
55         my( $s, $c, $a, $i ) = @_;
56         my $e = new_editor(authtoken => $a);
57         return $e->event unless $e->checkauth;
58         my $circ = $e->retrieve_action_circulation($i) or return $e->event;
59         if( $e->requestor->id ne $circ->usr ) {
60                 return $e->event unless $e->allowed('VIEW_CIRCULATIONS');
61         }
62         return $circ;
63 }
64
65
66 __PACKAGE__->register_method(
67         method => 'fetch_circ_mods',
68         api_name => 'open-ils.circ.circ_modifier.retrieve.all');
69 sub fetch_circ_mods {
70     my($self, $conn, $args) = @_;
71     my $mods = new_editor()->retrieve_all_config_circ_modifier;
72     return [ map {$_->code} @$mods ] unless $$args{full};
73     return $mods;
74 }
75
76 __PACKAGE__->register_method(
77         method => 'fetch_bill_types',
78         api_name => 'open-ils.circ.billing_type.retrieve.all');
79 sub fetch_bill_types {
80         my $conf = OpenSRF::Utils::SettingsClient->new;
81         return $conf->config_value(
82                 'apps', 'open-ils.circ', 'app_settings', 'billing_types', 'type' );
83 }
84
85
86 __PACKAGE__->register_method(
87     method => 'ranged_billing_types',
88     api_name => 'open-ils.circ.billing_type.ranged.retrieve.all');
89
90 sub ranged_billing_types {
91     my($self, $conn, $auth, $org_id, $depth) = @_;
92     my $e = new_editor(authtoken => $auth);
93     return $e->event unless $e->checkauth;
94     return $e->event unless $e->allowed('VIEW_BILLING_TYPE', $org_id);
95     return $e->search_config_billing_type(
96         {owner => $U->get_org_full_path($org_id, $depth)});
97 }
98
99
100
101 # ------------------------------------------------------------------------
102 # Returns an array of {circ, record} hashes checked out by the user.
103 # ------------------------------------------------------------------------
104 __PACKAGE__->register_method(
105         method  => "checkouts_by_user",
106         api_name        => "open-ils.circ.actor.user.checked_out",
107         NOTES           => <<"  NOTES");
108         Returns a list of open circulations as a pile of objects.  each object
109         contains the relevant copy, circ, and record
110         NOTES
111
112 sub checkouts_by_user {
113         my( $self, $client, $user_session, $user_id ) = @_;
114
115         my( $requestor, $target, $copy, $record, $evt );
116
117         ( $requestor, $target, $evt ) = 
118                 $apputils->checkses_requestor( $user_session, $user_id, 'VIEW_CIRCULATIONS');
119         return $evt if $evt;
120
121         my $circs = $apputils->simplereq(
122                 'open-ils.cstore',
123                 "open-ils.cstore.direct.action.open_circulation.search.atomic", 
124                 { usr => $target->id, checkin_time => undef } );
125 #               { usr => $target->id } );
126
127         my @results;
128         for my $circ (@$circs) {
129
130                 ( $copy, $evt )  = $apputils->fetch_copy($circ->target_copy);
131                 return $evt if $evt;
132
133                 $logger->debug("Retrieving record for copy " . $circ->target_copy);
134
135                 ($record, $evt) = $apputils->fetch_record_by_copy( $circ->target_copy );
136                 return $evt if $evt;
137
138                 my $mods = $apputils->record_to_mvr($record);
139
140                 push( @results, { copy => $copy, circ => $circ, record => $mods } );
141         }
142
143         return \@results;
144
145 }
146
147
148
149 __PACKAGE__->register_method(
150         method  => "checkouts_by_user_slim",
151         api_name        => "open-ils.circ.actor.user.checked_out.slim",
152         NOTES           => <<"  NOTES");
153         Returns a list of open circulation objects
154         NOTES
155
156 # DEPRECAT ME?? XXX
157 sub checkouts_by_user_slim {
158         my( $self, $client, $user_session, $user_id ) = @_;
159
160         my( $requestor, $target, $copy, $record, $evt );
161
162         ( $requestor, $target, $evt ) = 
163                 $apputils->checkses_requestor( $user_session, $user_id, 'VIEW_CIRCULATIONS');
164         return $evt if $evt;
165
166         $logger->debug( 'User ' . $requestor->id . 
167                 " retrieving checked out items for user " . $target->id );
168
169         # XXX Make the call correct..
170         return $apputils->simplereq(
171                 'open-ils.cstore',
172                 "open-ils.cstore.direct.action.open_circulation.search.atomic", 
173                 { usr => $target->id, checkin_time => undef } );
174 #               { usr => $target->id } );
175 }
176
177
178 __PACKAGE__->register_method(
179         method  => "checkouts_by_user_opac",
180         api_name        => "open-ils.circ.actor.user.checked_out.opac",);
181
182 # XXX Deprecate Me
183 sub checkouts_by_user_opac {
184         my( $self, $client, $auth, $user_id ) = @_;
185
186         my $e = OpenILS::Utils::Editor->new( authtoken => $auth );
187         return $e->event unless $e->checkauth;
188         $user_id ||= $e->requestor->id;
189         return $e->event unless 
190                 my $patron = $e->retrieve_actor_user($user_id);
191
192         my $data;
193         my $search = {usr => $user_id, stop_fines => undef};
194
195         if( $user_id ne $e->requestor->id ) {
196                 $data = $e->search_action_circulation(
197                         $search, {checkperm=>1, permorg=>$patron->home_ou})
198                         or return $e->event;
199
200         } else {
201                 $data = $e->search_action_circulation($search);
202         }
203
204         return $data;
205 }
206
207
208 __PACKAGE__->register_method(
209         method  => "title_from_transaction",
210         api_name        => "open-ils.circ.circ_transaction.find_title",
211         NOTES           => <<"  NOTES");
212         Returns a mods object for the title that is linked to from the 
213         copy from the hold that created the given transaction
214         NOTES
215
216 sub title_from_transaction {
217         my( $self, $client, $login_session, $transactionid ) = @_;
218
219         my( $user, $circ, $title, $evt );
220
221         ( $user, $evt ) = $apputils->checkses( $login_session );
222         return $evt if $evt;
223
224         ( $circ, $evt ) = $apputils->fetch_circulation($transactionid);
225         return $evt if $evt;
226         
227         ($title, $evt) = $apputils->fetch_record_by_copy($circ->target_copy);
228         return $evt if $evt;
229
230         return $apputils->record_to_mvr($title);
231 }
232
233
234
235 __PACKAGE__->register_method(
236         method  => "new_set_circ_lost",
237         api_name        => "open-ils.circ.circulation.set_lost",
238         signature       => q/
239         Sets the copy and related open circulation to lost
240                 @param auth
241                 @param args : barcode
242         /
243 );
244
245
246 # ---------------------------------------------------------------------
247 # Sets a circulation to lost.  updates copy status to lost
248 # applies copy and/or prcoessing fees depending on org settings
249 # ---------------------------------------------------------------------
250 sub new_set_circ_lost {
251     my( $self, $conn, $auth, $args ) = @_;
252
253     my $e = new_editor(authtoken=>$auth, xact=>1);
254     return $e->die_event unless $e->checkauth;
255
256     my $barcode = $$args{barcode};
257     $logger->info("marking item lost $barcode");
258
259     # ---------------------------------------------------------------------
260     # gather the pieces
261     my $copy = $e->search_asset_copy([
262         {barcode=>$barcode, deleted=>'f'},
263         {flesh => 1, flesh_fields => {'acp' => ['call_number']}}])->[0] 
264             or return $e->die_event;
265
266     my $owning_lib = 
267         ($copy->call_number->id == OILS_PRECAT_CALL_NUMBER) ? 
268             $copy->circ_lib : $copy->call_number->owning_lib;
269
270     my $circ = $e->search_action_circulation(
271         {checkin_time => undef, target_copy => $copy->id} )->[0]
272             or return $e->die_event;
273
274     $e->allowed('SET_CIRC_LOST', $circ->circ_lib) or return $e->die_event;
275
276     return OpenILS::Event->new('COPY_MARKED_LOST')
277             if $copy->status == OILS_COPY_STATUS_LOST;
278
279     # ---------------------------------------------------------------------
280     # fetch the related org settings
281     my $proc_fee = $U->ou_ancestor_setting_value(
282         $owning_lib, OILS_SETTING_LOST_PROCESSING_FEE, $e) || 0;
283     my $void_overdue = $U->ou_ancestor_setting_value(
284         $owning_lib, OILS_SETTING_VOID_OVERDUE_ON_LOST, $e) || 0;
285
286     # ---------------------------------------------------------------------
287     # move the copy into LOST status
288     $copy->status(OILS_COPY_STATUS_LOST);
289     $copy->editor($e->requestor->id);
290     $copy->edit_date('now');
291     $e->update_asset_copy($copy) or return $e->die_event;
292
293     my $price = $U->get_copy_price($e, $copy, $copy->call_number);
294
295     if( $price > 0 ) {
296         my $evt = create_bill($e, $price, 'Lost Materials', $circ->id);
297         return $evt if $evt;
298     }
299
300     # ---------------------------------------------------------------------
301     # if there is a processing fee, charge that too
302     if( $proc_fee > 0 ) {
303         my $evt = create_bill($e, $proc_fee, 'Lost Materials Processing Fee', $circ->id);
304         return $evt if $evt;
305     }
306
307     # ---------------------------------------------------------------------
308     # mark the circ as lost and stop the fines
309     $circ->stop_fines(OILS_STOP_FINES_LOST);
310     $circ->stop_fines_time('now') unless $circ->stop_fines_time;
311     $e->update_action_circulation($circ) or return $e->die_event;
312
313     # ---------------------------------------------------------------------
314     # void all overdue fines on this circ if configured
315     if( $void_overdue ) {
316         my $evt = void_overdues($e, $circ);
317         return $evt if $evt;
318     }
319
320     my $evt = reopen_xact($e, $circ->id);
321     return $evt if $evt;
322
323     $e->commit;
324     return 1;
325 }
326
327 sub reopen_xact {
328     my($e, $xactid) = @_;
329
330     # -----------------------------------------------------------------
331     # make sure the transaction is not closed
332     my $xact = $e->retrieve_money_billable_transaction($xactid)
333         or return $e->die_event;
334
335     if( $xact->xact_finish ) {
336         my ($mbts) = $U->fetch_mbts($xactid, $e);
337         if( $mbts->balance_owed != 0 ) {
338             $logger->info("* re-opening xact $xactid, orig xact_finish is ".$xact->xact_finish);
339             $xact->clear_xact_finish;
340             $e->update_money_billable_transaction($xact)
341                 or return $e->die_event;
342         } 
343     }
344
345     return undef;
346 }
347
348
349 sub create_bill {
350         my( $e, $amount, $type, $xactid ) = @_;
351
352         $logger->info("The system is charging $amount [$type] on xact $xactid");
353
354     # -----------------------------------------------------------------
355     # now create the billing
356         my $bill = Fieldmapper::money::billing->new;
357         $bill->xact($xactid);
358         $bill->amount($amount);
359         $bill->billing_type($type); 
360         $bill->note('SYSTEM GENERATED');
361     $e->create_money_billing($bill) or return $e->die_event;
362
363         return undef;
364 }
365
366
367
368 # -----------------------------------------------------------------
369 # Voids overdue fines on the given circ.  if a backdate is 
370 # provided, then we only void back to the backdate
371 # -----------------------------------------------------------------
372 sub void_overdues {
373     my( $e, $circ, $backdate ) = @_;
374
375     my $bill_search = { 
376         xact => $circ->id, 
377         billing_type => OILS_BILLING_TYPE_OVERDUE_MATERIALS 
378     };
379
380     if( $backdate ) {
381         # ------------------------------------------------------------------
382         # Fines for overdue materials are assessed up to, but not including,
383         # one fine interval after the fines are applicable.  Here, we add
384         # one fine interval to the backdate to ensure that we are not 
385         # voiding fines that were applicable before the backdate.
386         # ------------------------------------------------------------------
387
388         # if there is a raw time component (e.g. from postgres), 
389         # turn it into an interval that interval_to_seconds can parse
390         my $duration = $circ->fine_interval;
391         $duration =~ s/(\d{2}):(\d{2}):(\d{2})/$1 h $2 m $3 s/o;
392         my $interval = OpenSRF::Utils->interval_to_seconds($duration);
393
394         my $date = DateTime::Format::ISO8601->parse_datetime($backdate);
395         $backdate = $U->epoch2ISO8601($date->epoch + $interval);
396         $logger->info("applying backdate $backdate in overdue voiding");
397         $$bill_search{billing_ts} = {'>=' => $backdate};
398     }
399
400     my $bills = $e->search_money_billing($bill_search);
401     
402     for my $bill (@$bills) {
403         next if $U->is_true($bill->voided);
404         $logger->info("voiding overdue bill ".$bill->id);
405         $bill->voided('t');
406         $bill->void_time('now');
407         $bill->voider($e->requestor->id);
408         my $n = $bill->note || "";
409         $bill->note("$n\nSystem: VOIDED FOR BACKDATE");
410         $e->update_money_billing($bill) or return $e->die_event;
411     }
412
413         return undef;
414 }
415
416
417
418
419 __PACKAGE__->register_method(
420         method  => "set_circ_claims_returned",
421         api_name        => "open-ils.circ.circulation.set_claims_returned",
422         signature       => q/
423         Sets the circ for the given item as claims returned
424         If a backdate is provided, overdue fines will be voided
425         back to the backdate
426                 @param auth
427                 @param args : barcode, backdate
428         /
429 );
430
431 sub set_circ_claims_returned {
432     my( $self, $conn, $auth, $args ) = @_;
433
434     my $e = new_editor(authtoken=>$auth, xact=>1);
435     return $e->die_event unless $e->checkauth;
436
437     my $barcode = $$args{barcode};
438     my $backdate = $$args{backdate};
439
440     $logger->info("marking circ for item $barcode as claims returned".
441         (($backdate) ? " with backdate $backdate" : ''));
442
443     my $copy = $e->search_asset_copy({barcode=>$barcode, deleted=>'f'})->[0] 
444         or return $e->die_event;
445
446     my $circ = $e->search_action_circulation(
447         {checkin_time => undef, target_copy => $copy->id})->[0]
448             or return $e->die_event;
449
450     $e->allowed('SET_CIRC_CLAIMS_RETURNED', $circ->circ_lib) 
451         or return $e->die_event;
452
453     $circ->stop_fines(OILS_STOP_FINES_CLAIMSRETURNED);
454         $circ->stop_fines_time('now') unless $circ->stop_fines_time;
455
456     if( $backdate ) {
457         # make it look like the circ stopped at the cliams returned time
458         $circ->stop_fines_time(clense_ISO8601($backdate));
459         my $evt = void_overdues($e, $circ, $backdate);
460         return $evt if $evt;
461     }
462
463     $e->update_action_circulation($circ) or return $e->die_event;
464     $e->commit;
465     return 1;
466 }
467
468
469
470
471
472 __PACKAGE__->register_method (
473         method          => 'set_circ_due_date',
474         api_name                => 'open-ils.circ.circulation.due_date.update',
475         signature       => q/
476                 Updates the due_date on the given circ
477                 @param authtoken
478                 @param circid The id of the circ to update
479                 @param date The timestamp of the new due date
480         /
481 );
482
483 sub set_circ_due_date {
484         my( $self, $conn, $auth, $circ_id, $date ) = @_;
485
486     my $e = new_editor(xact=>1, authtoken=>$auth);
487     return $e->die_event unless $e->checkauth;
488     my $circ = $e->retrieve_action_circulation($circ_id)
489         or return $e->die_event;
490
491     return $e->die_event unless $e->allowed('CIRC_OVERRIDE_DUE_DATE', $circ->circ_lib);
492         $date = clense_ISO8601($date);
493         $circ->due_date($date);
494     $e->update_action_circulation($circ) or return $e->die_event;
495     $e->commit;
496
497     return $circ->id;
498 }
499
500
501 __PACKAGE__->register_method(
502         method          => "create_in_house_use",
503         api_name                => 'open-ils.circ.in_house_use.create',
504         signature       =>      q/
505                 Creates an in-house use action.
506                 @param $authtoken The login session key
507                 @param params A hash of params including
508                         'location' The org unit id where the in-house use occurs
509                         'copyid' The copy in question
510                         'count' The number of in-house uses to apply to this copy
511                 @return An array of id's representing the id's of the newly created
512                 in-house use objects or an event on an error
513         /);
514
515 __PACKAGE__->register_method(
516         method          => "create_in_house_use",
517         api_name                => 'open-ils.circ.non_cat_in_house_use.create',
518 );
519
520
521 sub create_in_house_use {
522         my( $self, $client, $auth, $params ) = @_;
523
524         my( $evt, $copy );
525         my $org                 = $params->{location};
526         my $copyid              = $params->{copyid};
527         my $count               = $params->{count} || 1;
528         my $nc_type             = $params->{non_cat_type};
529         my $use_time    = $params->{use_time} || 'now';
530
531         my $e = new_editor(xact=>1,authtoken=>$auth);
532         return $e->event unless $e->checkauth;
533         return $e->event unless $e->allowed('CREATE_IN_HOUSE_USE');
534
535         my $non_cat = 1 if $self->api_name =~ /non_cat/;
536
537         unless( $non_cat ) {
538                 if( $copyid ) {
539                         $copy = $e->retrieve_asset_copy($copyid) or return $e->event;
540                 } else {
541                         $copy = $e->search_asset_copy({barcode=>$params->{barcode}, deleted => 'f'})->[0]
542                                 or return $e->event;
543                         $copyid = $copy->id;
544                 }
545         }
546
547         if( $use_time ne 'now' ) {
548                 $use_time = clense_ISO8601($use_time);
549                 $logger->debug("in_house_use setting use time to $use_time");
550         }
551
552         my @ids;
553         for(1..$count) {
554
555                 my $ihu;
556                 my $method;
557                 my $cmeth;
558
559                 if($non_cat) {
560                         $ihu = Fieldmapper::action::non_cat_in_house_use->new;
561                         $ihu->item_type($nc_type);
562                         $method = 'open-ils.storage.direct.action.non_cat_in_house_use.create';
563                         $cmeth = "create_action_non_cat_in_house_use";
564
565                 } else {
566                         $ihu = Fieldmapper::action::in_house_use->new;
567                         $ihu->item($copyid);
568                         $method = 'open-ils.storage.direct.action.in_house_use.create';
569                         $cmeth = "create_action_in_house_use";
570                 }
571
572                 $ihu->staff($e->requestor->id);
573                 $ihu->org_unit($org);
574                 $ihu->use_time($use_time);
575
576                 $ihu = $e->$cmeth($ihu) or return $e->event;
577                 push( @ids, $ihu->id );
578         }
579
580         $e->commit;
581         return \@ids;
582 }
583
584
585
586
587
588 __PACKAGE__->register_method(
589         method  => "view_circs",
590         api_name        => "open-ils.circ.copy_checkout_history.retrieve",
591         notes           => q/
592                 Retrieves the last X circs for a given copy
593                 @param authtoken The login session key
594                 @param copyid The copy to check
595                 @param count How far to go back in the item history
596                 @return An array of circ ids
597         /);
598
599 # ----------------------------------------------------------------------
600 # Returns $count most recent circs.  If count exceeds the configured 
601 # max, use the configured max instead
602 # ----------------------------------------------------------------------
603 sub view_circs {
604         my( $self, $client, $authtoken, $copyid, $count ) = @_; 
605
606     my $e = new_editor(authtoken => $authtoken);
607     return $e->event unless $e->checkauth;
608     
609     my $copy = $e->retrieve_asset_copy([
610         $copyid,
611         {   flesh => 1,
612             flesh_fields => {acp => ['call_number']}
613         }
614     ]) or return $e->event;
615
616     return $e->event unless $e->allowed(
617         'VIEW_COPY_CHECKOUT_HISTORY', 
618         ($copy->call_number == OILS_PRECAT_CALL_NUMBER) ? 
619             $copy->circ_lib : $copy->call_number->owning_lib);
620         
621     my $max_history = $U->ou_ancestor_setting_value(
622         $e->requestor->ws_ou, 'circ.item_checkout_history.max', $e);
623
624     if(defined $max_history) {
625         $count = $max_history unless defined $count and $count < $max_history;
626     } else {
627         $count = 4 unless defined $count;
628     }
629
630     return $e->search_action_circulation([
631         {target_copy => $copyid}, 
632         {limit => $count, order_by => { circ => "xact_start DESC" }} 
633     ]);
634 }
635
636
637 __PACKAGE__->register_method(
638         method  => "circ_count",
639         api_name        => "open-ils.circ.circulation.count",
640         notes           => q/
641                 Returns the number of times the item has circulated
642                 @param copyid The copy to check
643         /);
644
645 sub circ_count {
646         my( $self, $client, $copyid, $range ) = @_; 
647         my $e = OpenILS::Utils::Editor->new;
648         return $e->request('open-ils.storage.asset.copy.circ_count', $copyid, $range);
649 }
650
651
652
653 __PACKAGE__->register_method(
654         method          => 'fetch_notes',
655         api_name                => 'open-ils.circ.copy_note.retrieve.all',
656         signature       => q/
657                 Returns an array of copy note objects.  
658                 @param args A named hash of parameters including:
659                         authtoken       : Required if viewing non-public notes
660                         itemid          : The id of the item whose notes we want to retrieve
661                         pub                     : True if all the caller wants are public notes
662                 @return An array of note objects
663         /);
664
665 __PACKAGE__->register_method(
666         method          => 'fetch_notes',
667         api_name                => 'open-ils.circ.call_number_note.retrieve.all',
668         signature       => q/@see open-ils.circ.copy_note.retrieve.all/);
669
670 __PACKAGE__->register_method(
671         method          => 'fetch_notes',
672         api_name                => 'open-ils.circ.title_note.retrieve.all',
673         signature       => q/@see open-ils.circ.copy_note.retrieve.all/);
674
675
676 # NOTE: VIEW_COPY/VOLUME/TITLE_NOTES perms should always be global
677 sub fetch_notes {
678         my( $self, $connection, $args ) = @_;
679
680         my $id = $$args{itemid};
681         my $authtoken = $$args{authtoken};
682         my( $r, $evt);
683
684         if( $self->api_name =~ /copy/ ) {
685                 if( $$args{pub} ) {
686                         return $U->cstorereq(
687                                 'open-ils.cstore.direct.asset.copy_note.search.atomic',
688                                 { owning_copy => $id, pub => 't' } );
689                 } else {
690                         ( $r, $evt ) = $U->checksesperm($authtoken, 'VIEW_COPY_NOTES');
691                         return $evt if $evt;
692                         return $U->cstorereq(
693                                 'open-ils.cstore.direct.asset.copy_note.search.atomic', {owning_copy => $id} );
694                 }
695
696         } elsif( $self->api_name =~ /call_number/ ) {
697                 if( $$args{pub} ) {
698                         return $U->cstorereq(
699                                 'open-ils.cstore.direct.asset.call_number_note.search.atomic',
700                                 { call_number => $id, pub => 't' } );
701                 } else {
702                         ( $r, $evt ) = $U->checksesperm($authtoken, 'VIEW_VOLUME_NOTES');
703                         return $evt if $evt;
704                         return $U->cstorereq(
705                                 'open-ils.cstore.direct.asset.call_number_note.search.atomic', { call_number => $id } );
706                 }
707
708         } elsif( $self->api_name =~ /title/ ) {
709                 if( $$args{pub} ) {
710                         return $U->cstorereq(
711                                 'open-ils.cstore.direct.bilbio.record_note.search.atomic',
712                                 { record => $id, pub => 't' } );
713                 } else {
714                         ( $r, $evt ) = $U->checksesperm($authtoken, 'VIEW_TITLE_NOTES');
715                         return $evt if $evt;
716                         return $U->cstorereq(
717                                 'open-ils.cstore.direct.biblio.record_note.search.atomic', { record => $id } );
718                 }
719         }
720
721         return undef;
722 }
723
724 __PACKAGE__->register_method(
725         method  => 'has_notes',
726         api_name        => 'open-ils.circ.copy.has_notes');
727 __PACKAGE__->register_method(
728         method  => 'has_notes',
729         api_name        => 'open-ils.circ.call_number.has_notes');
730 __PACKAGE__->register_method(
731         method  => 'has_notes',
732         api_name        => 'open-ils.circ.title.has_notes');
733
734
735 sub has_notes {
736         my( $self, $conn, $authtoken, $id ) = @_;
737         my $editor = OpenILS::Utils::Editor->new(authtoken => $authtoken);
738         return $editor->event unless $editor->checkauth;
739
740         my $n = $editor->search_asset_copy_note(
741                 {owning_copy=>$id}, {idlist=>1}) if $self->api_name =~ /copy/;
742
743         $n = $editor->search_asset_call_number_note(
744                 {call_number=>$id}, {idlist=>1}) if $self->api_name =~ /call_number/;
745
746         $n = $editor->search_biblio_record_note(
747                 {record=>$id}, {idlist=>1}) if $self->api_name =~ /title/;
748
749         return scalar @$n;
750 }
751
752
753
754 __PACKAGE__->register_method(
755         method          => 'create_copy_note',
756         api_name                => 'open-ils.circ.copy_note.create',
757         signature       => q/
758                 Creates a new copy note
759                 @param authtoken The login session key
760                 @param note     The note object to create
761                 @return The id of the new note object
762         /);
763
764 sub create_copy_note {
765         my( $self, $connection, $authtoken, $note ) = @_;
766
767         my $e = new_editor(xact=>1, authtoken=>$authtoken);
768         return $e->event unless $e->checkauth;
769         my $copy = $e->retrieve_asset_copy(
770                 [
771                         $note->owning_copy,
772                         {       flesh => 1,
773                                 flesh_fields => { 'acp' => ['call_number'] }
774                         }
775                 ]
776         );
777
778         return $e->event unless 
779                 $e->allowed('CREATE_COPY_NOTE', $copy->call_number->owning_lib);
780
781         $note->create_date('now');
782         $note->creator($e->requestor->id);
783         $note->pub( ($U->is_true($note->pub)) ? 't' : 'f' );
784         $note->clear_id;
785
786         $e->create_asset_copy_note($note) or return $e->event;
787         $e->commit;
788         return $note->id;
789 }
790
791
792 __PACKAGE__->register_method(
793         method          => 'delete_copy_note',
794         api_name                =>      'open-ils.circ.copy_note.delete',
795         signature       => q/
796                 Deletes an existing copy note
797                 @param authtoken The login session key
798                 @param noteid The id of the note to delete
799                 @return 1 on success - Event otherwise.
800                 /);
801 sub delete_copy_note {
802         my( $self, $conn, $authtoken, $noteid ) = @_;
803
804         my $e = new_editor(xact=>1, authtoken=>$authtoken);
805         return $e->die_event unless $e->checkauth;
806
807         my $note = $e->retrieve_asset_copy_note([
808                 $noteid,
809                 { flesh => 2,
810                         flesh_fields => {
811                                 'acpn' => [ 'owning_copy' ],
812                                 'acp' => [ 'call_number' ],
813                         }
814                 }
815         ]) or return $e->die_event;
816
817         if( $note->creator ne $e->requestor->id ) {
818                 return $e->die_event unless 
819                         $e->allowed('DELETE_COPY_NOTE', $note->owning_copy->call_number->owning_lib);
820         }
821
822         $e->delete_asset_copy_note($note) or return $e->die_event;
823         $e->commit;
824         return 1;
825 }
826
827
828 __PACKAGE__->register_method(
829         method => 'age_hold_rules',
830         api_name        =>  'open-ils.circ.config.rules.age_hold_protect.retrieve.all',
831 );
832
833 sub age_hold_rules {
834         my( $self, $conn ) = @_;
835         return new_editor()->retrieve_all_config_rules_age_hold_protect();
836 }
837
838
839
840 __PACKAGE__->register_method(
841         method => 'copy_details_barcode',
842     authoritative => 1,
843         api_name => 'open-ils.circ.copy_details.retrieve.barcode');
844 sub copy_details_barcode {
845         my( $self, $conn, $auth, $barcode ) = @_;
846     my $e = new_editor();
847     my $cid = $e->search_asset_copy({barcode=>$barcode, deleted=>'f'}, {idlist=>1})->[0];
848     return $e->event unless $cid;
849         return copy_details( $self, $conn, $auth, $cid );
850 }
851
852
853 __PACKAGE__->register_method(
854         method => 'copy_details',
855         api_name => 'open-ils.circ.copy_details.retrieve');
856
857 sub copy_details {
858         my( $self, $conn, $auth, $copy_id ) = @_;
859         my $e = new_editor(authtoken=>$auth);
860         return $e->event unless $e->checkauth;
861
862         my $flesh = { flesh => 1 };
863
864         my $copy = $e->retrieve_asset_copy(
865                 [
866                         $copy_id,
867                         {
868                                 flesh => 2,
869                                 flesh_fields => {
870                                         acp => ['call_number'],
871                                         acn => ['record']
872                                 }
873                         }
874                 ]) or return $e->event;
875
876
877         # De-flesh the copy for backwards compatibility
878         my $mvr;
879         my $vol = $copy->call_number;
880         if( ref $vol ) {
881                 $copy->call_number($vol->id);
882                 my $record = $vol->record;
883                 if( ref $record ) {
884                         $vol->record($record->id);
885                         $mvr = $U->record_to_mvr($record);
886                 }
887         }
888
889
890         my $hold = $e->search_action_hold_request(
891                 { 
892                         current_copy            => $copy_id, 
893                         capture_time            => { "!=" => undef },
894                         fulfillment_time        => undef,
895                         cancel_time                     => undef,
896                 }
897         )->[0];
898
899         OpenILS::Application::Circ::Holds::flesh_hold_transits([$hold]) if $hold;
900
901         my $transit = $e->search_action_transit_copy(
902                 { target_copy => $copy_id, dest_recv_time => undef } )->[0];
903
904         # find the latest circ, open or closed
905         my $circ = $e->search_action_circulation(
906                 [
907                         { target_copy => $copy_id },
908                         { order_by => { circ => 'xact_start desc' }, limit => 1 }
909                 ]
910         )->[0];
911
912
913         return {
914                 copy            => $copy,
915                 hold            => $hold,
916                 transit => $transit,
917                 circ            => $circ,
918                 volume  => $vol,
919                 mvr             => $mvr,
920         };
921 }
922
923
924
925
926 __PACKAGE__->register_method(
927         method => 'mark_item',
928         api_name => 'open-ils.circ.mark_item_damaged',
929 );
930 __PACKAGE__->register_method(
931         method => 'mark_item',
932         api_name => 'open-ils.circ.mark_item_missing',
933 );
934
935 sub mark_item {
936         my( $self, $conn, $auth, $copy_id ) = @_;
937         my $e = new_editor(authtoken=>$auth, xact =>1);
938         return $e->event unless $e->checkauth;
939
940         my $perm = 'MARK_ITEM_MISSING';
941         my $stat = OILS_COPY_STATUS_MISSING;
942
943         if( $self->api_name =~ /damaged/ ) {
944                 $perm = 'MARK_ITEM_DAMAGED';
945                 $stat = OILS_COPY_STATUS_DAMAGED;
946         }
947
948         my $copy = $e->retrieve_asset_copy($copy_id)
949                 or return $e->event;
950         $copy->status($stat);
951         $copy->edit_date('now');
952         $copy->editor($e->requestor->id);
953
954         $e->update_asset_copy($copy) or return $e->event;
955
956
957         my $holds = $e->search_action_hold_request(
958                 { 
959                         current_copy => $copy->id,
960                         fulfillment_time => undef,
961                         cancel_time => undef,
962                 }
963         );
964
965         $e->commit;
966
967         $logger->debug("reseting holds that target the marked copy");
968         OpenILS::Application::Circ::Holds->_reset_hold($e->requestor, $_) for @$holds;
969
970         return 1;
971 }
972
973
974
975
976
977
978 # ----------------------------------------------------------------------
979 __PACKAGE__->register_method(
980         method => 'magic_fetch',
981         api_name => 'open-ils.agent.fetch'
982 );
983
984 my @FETCH_ALLOWED = qw/ aou aout acp acn bre /;
985
986 sub magic_fetch {
987         my( $self, $conn, $auth, $args ) = @_;
988         my $e = new_editor( authtoken => $auth );
989         return $e->event unless $e->checkauth;
990
991         my $hint = $$args{hint};
992         my $id  = $$args{id};
993
994         # Is the call allowed to fetch this type of object?
995         return undef unless grep { $_ eq $hint } @FETCH_ALLOWED;
996
997         # Find the class the iplements the given hint
998         my ($class) = grep { 
999                 $Fieldmapper::fieldmap->{$_}{hint} eq $hint } Fieldmapper->classes;
1000
1001         $class =~ s/Fieldmapper:://og;
1002         $class =~ s/::/_/og;
1003         my $method = "retrieve_$class";
1004
1005         my $obj = $e->$method($id) or return $e->event;
1006         return $obj;
1007 }
1008 # ----------------------------------------------------------------------
1009
1010
1011 __PACKAGE__->register_method(
1012         method  => "fleshed_circ_retrieve",
1013     authoritative => 1,
1014         api_name        => "open-ils.circ.fleshed.retrieve",);
1015
1016 sub fleshed_circ_retrieve {
1017         my( $self, $client, $id ) = @_;
1018         my $e = new_editor();
1019         my $circ = $e->retrieve_action_circulation(
1020                 [
1021                         $id,
1022                         { 
1023                                 flesh                           => 4,
1024                                 flesh_fields    => { 
1025                                         circ => [ qw/ target_copy / ],
1026                                         acp => [ qw/ location status stat_cat_entry_copy_maps notes age_protect call_number / ],
1027                                         ascecm => [ qw/ stat_cat stat_cat_entry / ],
1028                                         acn => [ qw/ record / ],
1029                                 }
1030                         }
1031                 ]
1032         ) or return $e->event;
1033         
1034         my $copy = $circ->target_copy;
1035         my $vol = $copy->call_number;
1036         my $rec = $circ->target_copy->call_number->record;
1037
1038         $vol->record($rec->id);
1039         $copy->call_number($vol->id);
1040         $circ->target_copy($copy->id);
1041
1042         my $mvr;
1043
1044         if( $rec->id == OILS_PRECAT_RECORD ) {
1045                 $rec = undef;
1046                 $vol = undef;
1047         } else { 
1048                 $mvr = $U->record_to_mvr($rec);
1049                 $rec->marc(''); # drop the bulky marc data
1050         }
1051
1052         return {
1053                 circ => $circ,
1054                 copy => $copy,
1055                 volume => $vol,
1056                 record => $rec,
1057                 mvr => $mvr,
1058         };
1059 }
1060
1061 # {"select":{"acp":["id"],"circ":[{"aggregate":true,"transform":"count","alias":"count","column":"id"}]},"from":{"acp":{"circ":{"field":"target_copy","fkey":"id","type":"left"},"acn"{"field":"id","fkey":"call_number"}}},"where":{"+acn":{"record":200057}}
1062
1063
1064 1;