]> git.evergreen-ils.org Git - Evergreen.git/blob - Open-ILS/src/perlmods/lib/OpenILS/Application/Actor/Container.pm
LP 1437292: Order Bucket Names Alphabetically
[Evergreen.git] / Open-ILS / src / perlmods / lib / OpenILS / Application / Actor / Container.pm
1 package OpenILS::Application::Actor::Container;
2 use base 'OpenILS::Application';
3 use strict; use warnings;
4 use OpenILS::Application::AppUtils;
5 use OpenILS::Perm;
6 use Data::Dumper;
7 use OpenSRF::EX qw(:try);
8 use OpenILS::Utils::Fieldmapper;
9 use OpenILS::Utils::CStoreEditor qw/:funcs/;
10 use OpenSRF::Utils::SettingsClient;
11 use OpenSRF::Utils::Cache;
12 use Digest::MD5 qw(md5_hex);
13 use OpenSRF::Utils::JSON;
14
15 my $apputils = "OpenILS::Application::AppUtils";
16 my $U = $apputils;
17 my $logger = "OpenSRF::Utils::Logger";
18
19 sub initialize { return 1; }
20
21 my $svc = 'open-ils.cstore';
22 my $meth = 'open-ils.cstore.direct.container';
23 my %types;
24 my %ctypes;
25 $types{'biblio'} = "$meth.biblio_record_entry_bucket";
26 $types{'callnumber'} = "$meth.call_number_bucket";
27 $types{'copy'} = "$meth.copy_bucket";
28 $types{'user'} = "$meth.user_bucket";
29 $ctypes{'biblio'} = "container_biblio_record_entry_bucket";
30 $ctypes{'callnumber'} = "container_call_number_bucket";
31 $ctypes{'copy'} = "container_copy_bucket";
32 $ctypes{'user'} = "container_user_bucket";
33 my $event;
34
35 sub _sort_buckets {
36     my $buckets = shift;
37     return $buckets unless ($buckets && $buckets->[0]);
38     return [ sort { $a->name cmp $b->name } @$buckets ];
39 }
40
41 __PACKAGE__->register_method(
42     method  => "bucket_retrieve_all",
43     api_name    => "open-ils.actor.container.all.retrieve_by_user",
44     authoritative => 1,
45     notes        => <<"    NOTES");
46         Retrieves all un-fleshed buckets assigned to given user 
47         PARAMS(authtoken, bucketOwnerId)
48         If requestor ID is different than bucketOwnerId, requestor must have
49         VIEW_CONTAINER permissions.
50     NOTES
51
52 sub bucket_retrieve_all {
53     my($self, $client, $auth, $user_id) = @_;
54     my $e = new_editor(authtoken => $auth);
55     return $e->event unless $e->checkauth;
56
57     if($e->requestor->id ne $user_id) {
58         return $e->event unless $e->allowed('VIEW_CONTAINER');
59     }
60     
61     my %buckets;
62     for my $type (keys %ctypes) {
63         my $meth = "search_" . $ctypes{$type};
64         $buckets{$type} = _sort_buckets($e->$meth({owner => $user_id}));
65     }
66
67     return \%buckets;
68 }
69
70 __PACKAGE__->register_method(
71     method  => "bucket_flesh",
72     api_name    => "open-ils.actor.container.flesh",
73     authoritative => 1,
74     argc        => 3, 
75 );
76
77 __PACKAGE__->register_method(
78     method  => "bucket_flesh_pub",
79     api_name    => "open-ils.actor.container.public.flesh",
80     argc        => 3, 
81 );
82
83 sub bucket_flesh {
84     my($self, $conn, $auth, $class, $bucket_id) = @_;
85     my $e = new_editor(authtoken => $auth);
86     return $e->event unless $e->checkauth;
87     return _bucket_flesh($self, $conn, $e, $class, $bucket_id);
88 }
89
90 sub bucket_flesh_pub {
91     my($self, $conn, $class, $bucket_id) = @_;
92     my $e = new_editor();
93     return _bucket_flesh($self, $conn, $e, $class, $bucket_id);
94 }
95
96 sub _bucket_flesh {
97     my($self, $conn, $e, $class, $bucket_id) = @_;
98     my $meth = 'retrieve_' . $ctypes{$class};
99     my $bkt = $e->$meth($bucket_id) or return $e->event;
100
101     unless($U->is_true($bkt->pub)) {
102         return undef if $self->api_name =~ /public/;
103         unless($bkt->owner eq $e->requestor->id) {
104             my $owner = $e->retrieve_actor_user($bkt->owner)
105                 or return $e->die_event;
106             return $e->event unless $e->allowed('VIEW_CONTAINER', $owner->home_ou);
107         }
108     }
109
110     my $fmclass = $bkt->class_name . "i";
111     $meth = 'search_' . $ctypes{$class} . '_item';
112     $bkt->items(
113         $e->$meth(
114             {bucket => $bucket_id}, 
115             {   order_by => {$fmclass => "pos"},
116                 flesh => 1, 
117                 flesh_fields => {$fmclass => ['notes']}
118             }
119         )
120     );
121
122     return $bkt;
123 }
124
125
126 __PACKAGE__->register_method(
127     method  => "item_note_cud",
128     api_name    => "open-ils.actor.container.item_note.cud",
129 );
130
131
132 sub item_note_cud {
133     my($self, $conn, $auth, $class, $note) = @_;
134
135     return new OpenILS::Event("BAD_PARAMS") unless
136         $note->class_name =~ /bucket_item_note$/;
137
138     my $e = new_editor(authtoken => $auth, xact => 1);
139     return $e->die_event unless $e->checkauth;
140
141     my $meat = $ctypes{$class} . "_item_note";
142     my $meth = "retrieve_$meat";
143
144     my $item_meat = $ctypes{$class} . "_item";
145     my $item_meth = "retrieve_$item_meat";
146
147     my $nhint = $Fieldmapper::fieldmap->{$note->class_name}->{hint};
148     (my $ihint = $nhint) =~ s/n$//og;
149
150     my ($db_note, $item);
151
152     if ($note->isnew) {
153         $db_note = $note;
154
155         $item = $e->$item_meth([
156             $note->item, {
157                 flesh => 1, flesh_fields => {$ihint => ["bucket"]}
158             }
159         ]) or return $e->die_event;
160     } else {
161         $db_note = $e->$meth([
162             $note->id, {
163                 flesh => 2,
164                 flesh_fields => {
165                     $nhint => ['item'],
166                     $ihint => ['bucket']
167                 }
168             }
169         ]) or return $e->die_event;
170
171         $item = $db_note->item;
172     }
173
174     if($item->bucket->owner ne $e->requestor->id) {
175         return $e->die_event unless $e->allowed("UPDATE_CONTAINER");
176     }
177
178     $meth = 'create_' . $meat if $note->isnew;
179     $meth = 'update_' . $meat if $note->ischanged;
180     $meth = 'delete_' . $meat if $note->isdeleted;
181     return $e->die_event unless $e->$meth($note);
182     $e->commit;
183 }
184
185
186 __PACKAGE__->register_method(
187     method  => "bucket_retrieve_class",
188     api_name    => "open-ils.actor.container.retrieve_by_class",
189     argc        => 3, 
190     authoritative   => 1, 
191     notes        => <<"    NOTES");
192         Retrieves all un-fleshed buckets by class assigned to given user 
193         PARAMS(authtoken, bucketOwnerId, class [, type])
194         class can be one of "biblio", "callnumber", "copy", "user"
195         The optional "type" parameter allows you to limit the search by 
196         bucket type.  
197         If bucketOwnerId is not defined, the authtoken is used as the
198         bucket owner.
199         If requestor ID is different than bucketOwnerId, requestor must have
200         VIEW_CONTAINER permissions.
201     NOTES
202
203 sub bucket_retrieve_class {
204     my( $self, $client, $authtoken, $userid, $class, $type ) = @_;
205
206     my( $staff, $user, $evt ) = 
207         $apputils->checkses_requestor( $authtoken, $userid, 'VIEW_CONTAINER' );
208     return $evt if $evt;
209
210     $logger->debug("User " . $staff->id . 
211         " retrieving buckets for user $userid [class=$class, type=$type]");
212
213     my $meth = $types{$class} . ".search.atomic";
214     my $buckets;
215
216     if( $type ) {
217         $buckets = $apputils->simplereq( $svc, 
218             $meth, { owner => $userid, btype => $type } );
219     } else {
220         $logger->debug("Grabbing buckets by class $class: $svc : $meth :  {owner => $userid}");
221         $buckets = $apputils->simplereq( $svc, $meth, { owner => $userid } );
222     }
223
224     return _sort_buckets($buckets);
225 }
226
227 __PACKAGE__->register_method(
228     method  => "bucket_create",
229     api_name    => "open-ils.actor.container.create",
230     notes        => <<"    NOTES");
231         Creates a new bucket object.  If requestor is different from
232         bucketOwner, requestor needs CREATE_CONTAINER permissions
233         PARAMS(authtoken, bucketObject);
234         Returns the new bucket object
235     NOTES
236
237 sub bucket_create {
238     my( $self, $client, $authtoken, $class, $bucket ) = @_;
239
240     my $e = new_editor(xact=>1, authtoken=>$authtoken);
241     return $e->event unless $e->checkauth;
242
243     if( $bucket->owner ne $e->requestor->id ) {
244         return $e->event unless
245             $e->allowed('CREATE_CONTAINER');
246
247     } else {
248         return $e->event unless
249             $e->allowed('CREATE_MY_CONTAINER');
250     }
251         
252     $bucket->clear_id;
253
254     my $evt = OpenILS::Event->new('CONTAINER_EXISTS', 
255         payload => [$class, $bucket->owner, $bucket->btype, $bucket->name]);
256     my $search = {name => $bucket->name, owner => $bucket->owner, btype => $bucket->btype};
257
258     my $obj;
259     if( $class eq 'copy' ) {
260         return $evt if $e->search_container_copy_bucket($search)->[0];
261         return $e->event unless
262             $obj = $e->create_container_copy_bucket($bucket);
263     }
264
265     if( $class eq 'callnumber' ) {
266         return $evt if $e->search_container_call_number_bucket($search)->[0];
267         return $e->event unless
268             $obj = $e->create_container_call_number_bucket($bucket);
269     }
270
271     if( $class eq 'biblio' ) {
272         return $evt if $e->search_container_biblio_record_entry_bucket($search)->[0];
273         return $e->event unless
274             $obj = $e->create_container_biblio_record_entry_bucket($bucket);
275     }
276
277     if( $class eq 'user') {
278         return $evt if $e->search_container_user_bucket($search)->[0];
279         return $e->event unless
280             $obj = $e->create_container_user_bucket($bucket);
281     }
282
283     $e->commit;
284     return $obj->id;
285 }
286
287
288 __PACKAGE__->register_method(
289     method  => "item_create",
290     api_name    => "open-ils.actor.container.item.create",
291     signature => {
292         desc => q/
293             Adds one or more items to an existing container
294         /,
295         params => [
296             {desc => 'Authentication token', type => 'string'},
297             {desc => 'Container class.  Can be "copy", "callnumber", "biblio", or "user"', type => 'string'},
298             {desc => 'Item or items.  Can either be a single container item object, or an array of them', type => 'object'},
299         ],
300         return => {
301             desc => 'The ID of the newly created item(s).  In batch context, an array of IDs is returned'
302         }
303     }
304 );
305
306
307 sub item_create {
308     my( $self, $client, $authtoken, $class, $item ) = @_;
309
310     my $e = new_editor(xact=>1, authtoken=>$authtoken);
311     return $e->die_event unless $e->checkauth;
312     my $items = (ref $item eq 'ARRAY') ? $item : [$item];
313
314     my ( $bucket, $evt ) = $apputils->fetch_container_e($e, $item->bucket, $class);
315     return $evt if $evt;
316
317     if( $bucket->owner ne $e->requestor->id ) {
318         return $e->die_event unless
319             $e->allowed('CREATE_CONTAINER_ITEM');
320
321     } else {
322 #       return $e->event unless
323 #           $e->allowed('CREATE_CONTAINER_ITEM'); # new perm here?
324     }
325         
326     for my $one_item (@$items) {
327
328         $one_item->clear_id;
329
330         my $stat;
331         if( $class eq 'copy' ) {
332             return $e->die_event unless
333                 $stat = $e->create_container_copy_bucket_item($one_item);
334         }
335
336         if( $class eq 'callnumber' ) {
337             return $e->die_event unless
338                 $stat = $e->create_container_call_number_bucket_item($one_item);
339         }
340
341         if( $class eq 'biblio' ) {
342             return $e->die_event unless
343                 $stat = $e->create_container_biblio_record_entry_bucket_item($one_item);
344         }
345
346         if( $class eq 'user') {
347             return $e->die_event unless
348                 $stat = $e->create_container_user_bucket_item($one_item);
349         }
350     }
351
352     $e->commit;
353
354     # CStoreEeditor inserts the id (pkey) on newly created objects
355     return [ map { $_->id } @$items ] if ref $item eq 'ARRAY';
356     return $item->id; 
357 }
358
359
360
361 __PACKAGE__->register_method(
362     method  => "item_delete",
363     api_name    => "open-ils.actor.container.item.delete",
364     notes        => <<"    NOTES");
365         PARAMS(authtoken, class, itemId)
366     NOTES
367
368 sub item_delete {
369     my( $self, $client, $authtoken, $class, $itemid ) = @_;
370
371     my $e = new_editor(xact=>1, authtoken=>$authtoken);
372     return $e->event unless $e->checkauth;
373
374     my $ret = __item_delete($e, $class, $itemid);
375     $e->commit unless $U->event_code($ret);
376     return $ret;
377 }
378
379 sub __item_delete {
380     my( $e, $class, $itemid ) = @_;
381     my( $bucket, $item, $evt);
382
383     ( $item, $evt ) = $U->fetch_container_item_e( $e, $itemid, $class );
384     return $evt if $evt;
385
386     ( $bucket, $evt ) = $U->fetch_container_e($e, $item->bucket, $class);
387     return $evt if $evt;
388
389     if( $bucket->owner ne $e->requestor->id ) {
390       my $owner = $e->retrieve_actor_user($bucket->owner)
391          or return $e->die_event;
392         return $e->event unless $e->allowed('DELETE_CONTAINER_ITEM', $owner->home_ou);
393     }
394
395     my $stat;
396     if( $class eq 'copy' ) {
397         for my $note (@{$e->search_container_copy_bucket_item_note({item => $item->id})}) {
398             return $e->event unless 
399                 $e->delete_container_copy_bucket_item_note($note);
400         }
401         return $e->event unless
402             $stat = $e->delete_container_copy_bucket_item($item);
403     }
404
405     if( $class eq 'callnumber' ) {
406         for my $note (@{$e->search_container_call_number_bucket_item_note({item => $item->id})}) {
407             return $e->event unless 
408                 $e->delete_container_call_number_bucket_item_note($note);
409         }
410         return $e->event unless
411             $stat = $e->delete_container_call_number_bucket_item($item);
412     }
413
414     if( $class eq 'biblio' ) {
415         for my $note (@{$e->search_container_biblio_record_entry_bucket_item_note({item => $item->id})}) {
416             return $e->event unless 
417                 $e->delete_container_biblio_record_entry_bucket_item_note($note);
418         }
419         return $e->event unless
420             $stat = $e->delete_container_biblio_record_entry_bucket_item($item);
421     }
422
423     if( $class eq 'user') {
424         for my $note (@{$e->search_container_user_bucket_item_note({item => $item->id})}) {
425             return $e->event unless 
426                 $e->delete_container_user_bucket_item_note($note);
427         }
428         return $e->event unless
429             $stat = $e->delete_container_user_bucket_item($item);
430     }
431
432     return $stat;
433 }
434
435
436 __PACKAGE__->register_method(
437     method  => 'full_delete',
438     api_name    => 'open-ils.actor.container.full_delete',
439     notes       => "Complety removes a container including all attached items",
440 );  
441
442 sub full_delete {
443     my( $self, $client, $authtoken, $class, $containerId ) = @_;
444     my( $container, $evt);
445
446     my $e = new_editor(xact=>1, authtoken=>$authtoken);
447     return $e->event unless $e->checkauth;
448
449     ( $container, $evt ) = $apputils->fetch_container_e($e, $containerId, $class);
450     return $evt if $evt;
451
452     if( $container->owner ne $e->requestor->id ) {
453       my $owner = $e->retrieve_actor_user($container->owner)
454          or return $e->die_event;
455         return $e->event unless $e->allowed('DELETE_CONTAINER', $owner->home_ou);
456     }
457
458     my $items; 
459
460     my @s = ({bucket => $containerId}, {idlist=>1});
461
462     if( $class eq 'copy' ) {
463         $items = $e->search_container_copy_bucket_item(@s);
464     }
465
466     if( $class eq 'callnumber' ) {
467         $items = $e->search_container_call_number_bucket_item(@s);
468     }
469
470     if( $class eq 'biblio' ) {
471         $items = $e->search_container_biblio_record_entry_bucket_item(@s);
472     }
473
474     if( $class eq 'user') {
475         $items = $e->search_container_user_bucket_item(@s);
476     }
477
478     __item_delete($e, $class, $_) for @$items;
479
480     my $stat;
481     if( $class eq 'copy' ) {
482         return $e->event unless
483             $stat = $e->delete_container_copy_bucket($container);
484     }
485
486     if( $class eq 'callnumber' ) {
487         return $e->event unless
488             $stat = $e->delete_container_call_number_bucket($container);
489     }
490
491     if( $class eq 'biblio' ) {
492         return $e->event unless
493             $stat = $e->delete_container_biblio_record_entry_bucket($container);
494     }
495
496     if( $class eq 'user') {
497         return $e->event unless
498             $stat = $e->delete_container_user_bucket($container);
499     }
500
501     $e->commit;
502     return $stat;
503 }
504
505 __PACKAGE__->register_method(
506     method      => 'container_update',
507     api_name        => 'open-ils.actor.container.update',
508     signature   => q/
509         Updates the given container item.
510         @param authtoken The login session key
511         @param class The container class
512         @param container The container item
513         @return true on success, 0 on no update, Event on error
514         /
515 );
516
517 sub container_update {
518     my( $self, $conn, $authtoken, $class, $container )  = @_;
519
520     my $e = new_editor(xact=>1, authtoken=>$authtoken);
521     return $e->event unless $e->checkauth;
522
523     my ( $dbcontainer, $evt ) = $U->fetch_container_e($e, $container->id, $class);
524     return $evt if $evt;
525
526     if( $e->requestor->id ne $container->owner ) {
527         return $e->event unless $e->allowed('UPDATE_CONTAINER');
528     }
529
530     my $stat;
531     if( $class eq 'copy' ) {
532         return $e->event unless
533             $stat = $e->update_container_copy_bucket($container);
534     }
535
536     if( $class eq 'callnumber' ) {
537         return $e->event unless
538             $stat = $e->update_container_call_number_bucket($container);
539     }
540
541     if( $class eq 'biblio' ) {
542         return $e->event unless
543             $stat = $e->update_container_biblio_record_entry_bucket($container);
544     }
545
546     if( $class eq 'user') {
547         return $e->event unless
548             $stat = $e->update_container_user_bucket($container);
549     }
550
551     $e->commit;
552     return $stat;
553 }
554
555
556
557 __PACKAGE__->register_method(
558     method  => "anon_cache",
559     api_name    => "open-ils.actor.anon_cache.set_value",
560     signature => {
561         desc => q/
562             Sets a value in the anon web cache.  If the session key is
563             undefined, one will be automatically generated.
564         /,
565         params => [
566             {desc => 'Session key', type => 'string'},
567             {
568                 desc => q/Field name.  The name of the field in this cache session whose value to set/, 
569                 type => 'string'
570             },
571             {
572                 desc => q/The cached value.  This can be any type of object (hash, array, string, etc.)/,
573                 type => 'any'
574             },
575         ],
576         return => {
577             desc => 'session key on success, undef on error',
578             type => 'string'
579         }
580     }
581 );
582
583 __PACKAGE__->register_method(
584     method  => "anon_cache",
585     api_name    => "open-ils.actor.anon_cache.get_value",
586     signature => {
587         desc => q/
588             Returns the cached data at the specified field within the specified cache session.
589         /,
590         params => [
591             {desc => 'Session key', type => 'string'},
592             {
593                 desc => q/Field name.  The name of the field in this cache session whose value to set/, 
594                 type => 'string'
595             },
596         ],
597         return => {
598             desc => 'cached value on success, undef on error',
599             type => 'any'
600         }
601     }
602 );
603
604 __PACKAGE__->register_method(
605     method  => "anon_cache",
606     api_name    => "open-ils.actor.anon_cache.delete_session",
607     signature => {
608         desc => q/
609             Deletes a cache session.
610         /,
611         params => [
612             {desc => 'Session key', type => 'string'},
613         ],
614         return => {
615             desc => 'Session key',
616             type => 'string'
617         }
618     }
619 );
620
621 sub anon_cache {
622     my($self, $conn, $ses_key, $field_key, $value) = @_;
623
624     my $sc = OpenSRF::Utils::SettingsClient->new;
625     my $cache = OpenSRF::Utils::Cache->new('anon');
626     my $cache_timeout = $sc->config_value(cache => anon => 'max_cache_time') || 1800; # 30 minutes
627     my $cache_size = $sc->config_value(cache => anon => 'max_cache_size') || 102400; # 100k
628
629     if($self->api_name =~ /delete_session/) {
630
631        return $cache->delete_cache($ses_key); 
632
633     }  elsif( $self->api_name =~ /set_value/ ) {
634
635         $ses_key = md5_hex(time . rand($$)) unless $ses_key;
636         my $blob = $cache->get_cache($ses_key) || {};
637         $blob->{$field_key} = $value;
638         return undef if 
639             length(OpenSRF::Utils::JSON->perl2JSON($blob)) > $cache_size; # bytes, characters, whatever ;)
640         $cache->put_cache($ses_key, $blob, $cache_timeout);
641         return $ses_key;
642
643     } else {
644
645         my $blob = $cache->get_cache($ses_key) or return undef;
646         return $blob if (!defined($field_key));
647         return $blob->{$field_key};
648     }
649 }
650
651
652
653 1;
654
655