1 package OpenILS::Application::Actor::Container;
2 use base 'OpenILS::Application';
3 use strict; use warnings;
4 use OpenILS::Application::AppUtils;
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;
15 my $apputils = "OpenILS::Application::AppUtils";
17 my $logger = "OpenSRF::Utils::Logger";
19 sub initialize { return 1; }
21 my $svc = 'open-ils.cstore';
22 my $meth = 'open-ils.cstore.direct.container';
32 $batch_perm{'biblio'} = ['UPDATE_MARC'];
33 $batch_perm{'callnumber'} = ['UPDATE_VOLUME'];
34 $batch_perm{'copy'} = ['UPDATE_COPY'];
35 $batch_perm{'user'} = ['UPDATE_USER'];
37 $types{'biblio'} = "$meth.biblio_record_entry_bucket";
38 $types{'callnumber'} = "$meth.call_number_bucket";
39 $types{'copy'} = "$meth.copy_bucket";
40 $types{'user'} = "$meth.user_bucket";
42 $ctypes{'biblio'} = "container_biblio_record_entry_bucket";
43 $ctypes{'callnumber'} = "container_call_number_bucket";
44 $ctypes{'copy'} = "container_copy_bucket";
45 $ctypes{'user'} = "container_user_bucket";
47 $itypes{'biblio'} = "biblio_record_entry";
48 $itypes{'callnumber'} = "asset_call_number";
49 $itypes{'copy'} = "asset_copy";
50 $itypes{'user'} = "actor_user";
52 $ttypes{'biblio'} = "biblio_record_entry";
53 $ttypes{'callnumber'} = "call_number";
54 $ttypes{'copy'} = "copy";
55 $ttypes{'user'} = "user";
57 $htypes{'biblio'} = "bre";
58 $htypes{'callnumber'} = "acn";
59 $htypes{'copy'} = "acp";
60 $htypes{'user'} = "au";
62 $table{'biblio'} = "biblio.record_entry";
63 $table{'callnumber'} = "asset.call_number";
64 $table{'copy'} = "asset.copy";
65 $table{'user'} = "actor.usr";
67 #$qtypes{'biblio'} = 0
68 #$qtypes{'callnumber'} = 0;
76 return $buckets unless ($buckets && $buckets->[0]);
77 return [ sort { $a->name cmp $b->name } @$buckets ];
80 __PACKAGE__->register_method(
81 method => "bucket_retrieve_all",
82 api_name => "open-ils.actor.container.all.retrieve_by_user",
85 Retrieves all un-fleshed buckets assigned to given user
86 PARAMS(authtoken, bucketOwnerId)
87 If requestor ID is different than bucketOwnerId, requestor must have
88 VIEW_CONTAINER permissions.
91 sub bucket_retrieve_all {
92 my($self, $client, $auth, $user_id) = @_;
93 my $e = new_editor(authtoken => $auth);
94 return $e->event unless $e->checkauth;
96 if($e->requestor->id ne $user_id) {
97 return $e->event unless $e->allowed('VIEW_CONTAINER');
101 for my $type (keys %ctypes) {
102 my $meth = "search_" . $ctypes{$type};
103 $buckets{$type} = _sort_buckets($e->$meth({owner => $user_id}));
109 __PACKAGE__->register_method(
110 method => "bucket_flesh",
111 api_name => "open-ils.actor.container.flesh",
116 __PACKAGE__->register_method(
117 method => "bucket_flesh_pub",
118 api_name => "open-ils.actor.container.public.flesh",
123 my($self, $conn, $auth, $class, $bucket_id) = @_;
124 my $e = new_editor(authtoken => $auth);
125 return $e->event unless $e->checkauth;
126 return _bucket_flesh($self, $conn, $e, $class, $bucket_id);
129 sub bucket_flesh_pub {
130 my($self, $conn, $class, $bucket_id) = @_;
131 my $e = new_editor();
132 return _bucket_flesh($self, $conn, $e, $class, $bucket_id);
136 my($self, $conn, $e, $class, $bucket_id) = @_;
137 my $meth = 'retrieve_' . $ctypes{$class};
138 my $bkt = $e->$meth($bucket_id) or return $e->event;
140 unless($U->is_true($bkt->pub)) {
141 return undef if $self->api_name =~ /public/;
142 unless($bkt->owner eq $e->requestor->id) {
143 my $owner = $e->retrieve_actor_user($bkt->owner)
144 or return $e->die_event;
145 return $e->event unless $e->allowed('VIEW_CONTAINER', $owner->home_ou);
149 my $fmclass = $bkt->class_name . "i";
150 $meth = 'search_' . $ctypes{$class} . '_item';
153 {bucket => $bucket_id},
154 { order_by => {$fmclass => "pos"},
156 flesh_fields => {$fmclass => ['notes']}
165 __PACKAGE__->register_method(
166 method => "item_note_cud",
167 api_name => "open-ils.actor.container.item_note.cud",
172 my($self, $conn, $auth, $class, $note) = @_;
174 return new OpenILS::Event("BAD_PARAMS") unless
175 $note->class_name =~ /bucket_item_note$/;
177 my $e = new_editor(authtoken => $auth, xact => 1);
178 return $e->die_event unless $e->checkauth;
180 my $meat = $ctypes{$class} . "_item_note";
181 my $meth = "retrieve_$meat";
183 my $item_meat = $ctypes{$class} . "_item";
184 my $item_meth = "retrieve_$item_meat";
186 my $nhint = $Fieldmapper::fieldmap->{$note->class_name}->{hint};
187 (my $ihint = $nhint) =~ s/n$//og;
189 my ($db_note, $item);
194 $item = $e->$item_meth([
196 flesh => 1, flesh_fields => {$ihint => ["bucket"]}
198 ]) or return $e->die_event;
200 $db_note = $e->$meth([
208 ]) or return $e->die_event;
210 $item = $db_note->item;
213 if($item->bucket->owner ne $e->requestor->id) {
214 return $e->die_event unless $e->allowed("UPDATE_CONTAINER");
217 $meth = 'create_' . $meat if $note->isnew;
218 $meth = 'update_' . $meat if $note->ischanged;
219 $meth = 'delete_' . $meat if $note->isdeleted;
220 return $e->die_event unless $e->$meth($note);
225 __PACKAGE__->register_method(
226 method => "bucket_retrieve_class",
227 api_name => "open-ils.actor.container.retrieve_by_class",
230 notes => <<" NOTES");
231 Retrieves all un-fleshed buckets by class assigned to given user
232 PARAMS(authtoken, bucketOwnerId, class [, type])
233 class can be one of "biblio", "callnumber", "copy", "user"
234 The optional "type" parameter allows you to limit the search by
236 If bucketOwnerId is not defined, the authtoken is used as the
238 If requestor ID is different than bucketOwnerId, requestor must have
239 VIEW_CONTAINER permissions.
242 sub bucket_retrieve_class {
243 my( $self, $client, $authtoken, $userid, $class, $type ) = @_;
245 my( $staff, $user, $evt ) =
246 $apputils->checkses_requestor( $authtoken, $userid, 'VIEW_CONTAINER' );
249 $logger->debug("User " . $staff->id .
250 " retrieving buckets for user $userid [class=$class, type=$type]");
252 my $meth = $types{$class} . ".search.atomic";
256 $buckets = $apputils->simplereq( $svc,
257 $meth, { owner => $userid, btype => $type } );
259 $logger->debug("Grabbing buckets by class $class: $svc : $meth : {owner => $userid}");
260 $buckets = $apputils->simplereq( $svc, $meth, { owner => $userid } );
263 return _sort_buckets($buckets);
266 __PACKAGE__->register_method(
267 method => "bucket_create",
268 api_name => "open-ils.actor.container.create",
269 notes => <<" NOTES");
270 Creates a new bucket object. If requestor is different from
271 bucketOwner, requestor needs CREATE_CONTAINER permissions
272 PARAMS(authtoken, bucketObject);
273 Returns the new bucket object
277 my( $self, $client, $authtoken, $class, $bucket ) = @_;
279 my $e = new_editor(xact=>1, authtoken=>$authtoken);
280 return $e->event unless $e->checkauth;
282 if( $bucket->owner ne $e->requestor->id ) {
283 return $e->event unless
284 $e->allowed('CREATE_CONTAINER');
287 return $e->event unless
288 $e->allowed('CREATE_MY_CONTAINER');
293 my $evt = OpenILS::Event->new('CONTAINER_EXISTS',
294 payload => [$class, $bucket->owner, $bucket->btype, $bucket->name]);
295 my $search = {name => $bucket->name, owner => $bucket->owner, btype => $bucket->btype};
298 if( $class eq 'copy' ) {
299 return $evt if $e->search_container_copy_bucket($search)->[0];
300 return $e->event unless
301 $obj = $e->create_container_copy_bucket($bucket);
304 if( $class eq 'callnumber' ) {
305 return $evt if $e->search_container_call_number_bucket($search)->[0];
306 return $e->event unless
307 $obj = $e->create_container_call_number_bucket($bucket);
310 if( $class eq 'biblio' ) {
311 return $evt if $e->search_container_biblio_record_entry_bucket($search)->[0];
312 return $e->event unless
313 $obj = $e->create_container_biblio_record_entry_bucket($bucket);
316 if( $class eq 'user') {
317 return $evt if $e->search_container_user_bucket($search)->[0];
318 return $e->event unless
319 $obj = $e->create_container_user_bucket($bucket);
327 __PACKAGE__->register_method(
328 method => "item_create",
329 api_name => "open-ils.actor.container.item.create",
332 Adds one or more items to an existing container
335 {desc => 'Authentication token', type => 'string'},
336 {desc => 'Container class. Can be "copy", "callnumber", "biblio", or "user"', type => 'string'},
337 {desc => 'Item or items. Can either be a single container item object, or an array of them', type => 'object'},
340 desc => 'The ID of the newly created item(s). In batch context, an array of IDs is returned'
347 my( $self, $client, $authtoken, $class, $item ) = @_;
349 my $e = new_editor(xact=>1, authtoken=>$authtoken);
350 return $e->die_event unless $e->checkauth;
351 my $items = (ref $item eq 'ARRAY') ? $item : [$item];
353 my ( $bucket, $evt ) =
354 $apputils->fetch_container_e($e, $items->[0]->bucket, $class);
357 if( $bucket->owner ne $e->requestor->id ) {
358 return $e->die_event unless
359 $e->allowed('CREATE_CONTAINER_ITEM');
362 # return $e->event unless
363 # $e->allowed('CREATE_CONTAINER_ITEM'); # new perm here?
366 for my $one_item (@$items) {
371 if( $class eq 'copy' ) {
372 return $e->die_event unless
373 $stat = $e->create_container_copy_bucket_item($one_item);
376 if( $class eq 'callnumber' ) {
377 return $e->die_event unless
378 $stat = $e->create_container_call_number_bucket_item($one_item);
381 if( $class eq 'biblio' ) {
382 return $e->die_event unless
383 $stat = $e->create_container_biblio_record_entry_bucket_item($one_item);
386 if( $class eq 'user') {
387 return $e->die_event unless
388 $stat = $e->create_container_user_bucket_item($one_item);
394 # CStoreEeditor inserts the id (pkey) on newly created objects
395 return [ map { $_->id } @$items ] if ref $item eq 'ARRAY';
401 __PACKAGE__->register_method(
402 method => "item_delete",
403 api_name => "open-ils.actor.container.item.delete",
404 notes => <<" NOTES");
405 PARAMS(authtoken, class, itemId)
409 my( $self, $client, $authtoken, $class, $itemid ) = @_;
411 my $e = new_editor(xact=>1, authtoken=>$authtoken);
412 return $e->event unless $e->checkauth;
414 my $ret = __item_delete($e, $class, $itemid);
415 $e->commit unless $U->event_code($ret);
420 my( $e, $class, $itemid ) = @_;
421 my( $bucket, $item, $evt);
423 ( $item, $evt ) = $U->fetch_container_item_e( $e, $itemid, $class );
426 ( $bucket, $evt ) = $U->fetch_container_e($e, $item->bucket, $class);
429 if( $bucket->owner ne $e->requestor->id ) {
430 my $owner = $e->retrieve_actor_user($bucket->owner)
431 or return $e->die_event;
432 return $e->event unless $e->allowed('DELETE_CONTAINER_ITEM', $owner->home_ou);
436 if( $class eq 'copy' ) {
437 for my $note (@{$e->search_container_copy_bucket_item_note({item => $item->id})}) {
438 return $e->event unless
439 $e->delete_container_copy_bucket_item_note($note);
441 return $e->event unless
442 $stat = $e->delete_container_copy_bucket_item($item);
445 if( $class eq 'callnumber' ) {
446 for my $note (@{$e->search_container_call_number_bucket_item_note({item => $item->id})}) {
447 return $e->event unless
448 $e->delete_container_call_number_bucket_item_note($note);
450 return $e->event unless
451 $stat = $e->delete_container_call_number_bucket_item($item);
454 if( $class eq 'biblio' ) {
455 for my $note (@{$e->search_container_biblio_record_entry_bucket_item_note({item => $item->id})}) {
456 return $e->event unless
457 $e->delete_container_biblio_record_entry_bucket_item_note($note);
459 return $e->event unless
460 $stat = $e->delete_container_biblio_record_entry_bucket_item($item);
463 if( $class eq 'user') {
464 for my $note (@{$e->search_container_user_bucket_item_note({item => $item->id})}) {
465 return $e->event unless
466 $e->delete_container_user_bucket_item_note($note);
468 return $e->event unless
469 $stat = $e->delete_container_user_bucket_item($item);
476 __PACKAGE__->register_method(
477 method => 'full_delete',
478 api_name => 'open-ils.actor.container.full_delete',
479 notes => "Complety removes a container including all attached items",
483 my( $self, $client, $authtoken, $class, $containerId ) = @_;
484 my( $container, $evt);
486 my $e = new_editor(xact=>1, authtoken=>$authtoken);
487 return $e->event unless $e->checkauth;
489 ( $container, $evt ) = $apputils->fetch_container_e($e, $containerId, $class);
492 if( $container->owner ne $e->requestor->id ) {
493 my $owner = $e->retrieve_actor_user($container->owner)
494 or return $e->die_event;
495 return $e->event unless $e->allowed('DELETE_CONTAINER', $owner->home_ou);
500 my @s = ({bucket => $containerId}, {idlist=>1});
502 if( $class eq 'copy' ) {
503 $items = $e->search_container_copy_bucket_item(@s);
506 if( $class eq 'callnumber' ) {
507 $items = $e->search_container_call_number_bucket_item(@s);
510 if( $class eq 'biblio' ) {
511 $items = $e->search_container_biblio_record_entry_bucket_item(@s);
514 if( $class eq 'user') {
515 $items = $e->search_container_user_bucket_item(@s);
518 __item_delete($e, $class, $_) for @$items;
521 if( $class eq 'copy' ) {
522 return $e->event unless
523 $stat = $e->delete_container_copy_bucket($container);
526 if( $class eq 'callnumber' ) {
527 return $e->event unless
528 $stat = $e->delete_container_call_number_bucket($container);
531 if( $class eq 'biblio' ) {
532 return $e->event unless
533 $stat = $e->delete_container_biblio_record_entry_bucket($container);
536 if( $class eq 'user') {
537 return $e->event unless
538 $stat = $e->delete_container_user_bucket($container);
545 __PACKAGE__->register_method(
546 method => 'container_update',
547 api_name => 'open-ils.actor.container.update',
549 Updates the given container item.
550 @param authtoken The login session key
551 @param class The container class
552 @param container The container item
553 @return true on success, 0 on no update, Event on error
557 sub container_update {
558 my( $self, $conn, $authtoken, $class, $container ) = @_;
560 my $e = new_editor(xact=>1, authtoken=>$authtoken);
561 return $e->event unless $e->checkauth;
563 my ( $dbcontainer, $evt ) = $U->fetch_container_e($e, $container->id, $class);
566 if( $e->requestor->id ne $container->owner ) {
567 return $e->event unless $e->allowed('UPDATE_CONTAINER');
571 if( $class eq 'copy' ) {
572 return $e->event unless
573 $stat = $e->update_container_copy_bucket($container);
576 if( $class eq 'callnumber' ) {
577 return $e->event unless
578 $stat = $e->update_container_call_number_bucket($container);
581 if( $class eq 'biblio' ) {
582 return $e->event unless
583 $stat = $e->update_container_biblio_record_entry_bucket($container);
586 if( $class eq 'user') {
587 return $e->event unless
588 $stat = $e->update_container_user_bucket($container);
597 __PACKAGE__->register_method(
598 method => "anon_cache",
599 api_name => "open-ils.actor.anon_cache.set_value",
602 Sets a value in the anon web cache. If the session key is
603 undefined, one will be automatically generated.
606 {desc => 'Session key', type => 'string'},
608 desc => q/Field name. The name of the field in this cache session whose value to set/,
612 desc => q/The cached value. This can be any type of object (hash, array, string, etc.)/,
617 desc => 'session key on success, undef on error',
623 __PACKAGE__->register_method(
624 method => "anon_cache",
625 api_name => "open-ils.actor.anon_cache.get_value",
628 Returns the cached data at the specified field within the specified cache session.
631 {desc => 'Session key', type => 'string'},
633 desc => q/Field name. The name of the field in this cache session whose value to set/,
638 desc => 'cached value on success, undef on error',
644 __PACKAGE__->register_method(
645 method => "anon_cache",
646 api_name => "open-ils.actor.anon_cache.delete_session",
649 Deletes a cache session.
652 {desc => 'Session key', type => 'string'},
655 desc => 'Session key',
662 my($self, $conn, $ses_key, $field_key, $value) = @_;
664 my $sc = OpenSRF::Utils::SettingsClient->new;
665 my $cache = OpenSRF::Utils::Cache->new('anon');
666 my $cache_timeout = $sc->config_value(cache => anon => 'max_cache_time') || 1800; # 30 minutes
667 my $cache_size = $sc->config_value(cache => anon => 'max_cache_size') || 102400; # 100k
669 if($self->api_name =~ /delete_session/) {
671 return $cache->delete_cache($ses_key);
673 } elsif( $self->api_name =~ /set_value/ ) {
675 $ses_key = md5_hex(time . rand($$)) unless $ses_key;
676 my $blob = $cache->get_cache($ses_key) || {};
677 $blob->{$field_key} = $value;
679 length(OpenSRF::Utils::JSON->perl2JSON($blob)) > $cache_size; # bytes, characters, whatever ;)
680 $cache->put_cache($ses_key, $blob, $cache_timeout);
685 my $blob = $cache->get_cache($ses_key) or return undef;
686 return $blob if (!defined($field_key));
687 return $blob->{$field_key};
691 sub batch_statcat_apply {
698 # $changes is a hashref that looks like:
700 # remove => [ qw/ stat cat ids to remove / ],
701 # apply => { $statcat_id => $value_string, ... }
709 my $e = new_editor(xact=>1, authtoken=>$ses);
710 return $e->die_event unless $e->checkauth;
711 $client->respond({ ord => $stage++, stage => 'CONTAINER_BATCH_UPDATE_PERM_CHECK' });
712 return $e->die_event unless $e->allowed('CONTAINER_BATCH_UPDATE');
714 my $meth = 'retrieve_' . $ctypes{$class};
715 my $bkt = $e->$meth($c_id) or return $e->die_event;
717 unless($bkt->owner eq $e->requestor->id) {
718 $client->respond({ ord => $stage++, stage => 'CONTAINER_PERM_CHECK' });
719 my $owner = $e->retrieve_actor_user($bkt->owner)
720 or return $e->die_event;
721 return $e->die_event unless (
722 $e->allowed('VIEW_CONTAINER', $bkt->owning_lib) || $e->allowed('VIEW_CONTAINER', $owner->home_ou)
726 $meth = 'search_' . $ctypes{$class} . '_item';
727 my $contents = $e->$meth({bucket => $c_id});
729 if ($self->{perms}) {
730 $max = scalar(@$contents);
731 $client->respond({ ord => $stage, max => $max, count => 0, stage => 'ITEM_PERM_CHECK' });
732 for my $item (@$contents) {
734 $meth = 'retrieve_' . $itypes{$class};
735 my $field = 'target_'.$ttypes{$class};
736 my $obj = $e->$meth($item->$field);
738 for my $perm_field (keys %{$self->{perms}}) {
739 my $perm_def = $self->{perms}->{$perm_field};
740 my ($pwhat,$pwhere) = ([split ' ', $perm_def], $perm_field);
741 for my $p (@$pwhat) {
742 $e->allowed($p, $obj->$pwhere) or return $e->die_event;
745 $client->respond({ ord => $stage, max => $max, count => $count, stage => 'ITEM_PERM_CHECK' });
750 my @users = map { $_->target_user } @$contents;
751 $max = scalar(@users) * scalar(@{$changes->{remove}});
753 $client->respond({ ord => $stage, max => $max, count => $count, stage => 'STAT_CAT_REMOVE' });
755 my $chunk = int($max / 10) || 1;
756 my $to_remove = $e->search_actor_stat_cat_entry_user_map({ target_usr => \@users, stat_cat => $changes->{remove} });
757 for my $t (@$to_remove) {
758 $e->delete_actor_stat_cat_entry_user_map($t);
760 $client->respond({ ord => $stage, max => $max, count => $count, stage => 'STAT_CAT_REMOVE' })
761 unless ($count % $chunk);
766 $max = scalar(@users) * scalar(keys %{$changes->{apply}});
768 $client->respond({ ord => $stage, max => $max, count => $count, stage => 'STAT_CAT_APPLY' });
770 $chunk = int($max / 10) || 1;
771 for my $item (@$contents) {
772 for my $astatcat (keys %{$changes->{apply}}) {
773 my $new_value = $changes->{apply}->{$astatcat};
774 my $to_change = $e->search_actor_stat_cat_entry_user_map({ target_usr => $item->target_user, stat_cat => $astatcat });
776 $to_change = $$to_change[0];
777 $to_change->stat_cat_entry($new_value);
778 $e->update_actor_stat_cat_entry_user_map($to_change);
780 $to_change = new Fieldmapper::actor::stat_cat_entry_user_map;
781 $to_change->stat_cat_entry($new_value);
782 $to_change->stat_cat($astatcat);
783 $to_change->target_usr($item->target_user);
784 $e->create_actor_stat_cat_entry_user_map($to_change);
787 $client->respond({ ord => $stage, max => $max, count => $count, stage => 'STAT_CAT_APPLY' })
788 unless ($count % $chunk);
794 return { stage => 'COMPLETE' };
797 __PACKAGE__->register_method(
798 method => "batch_statcat_apply",
799 api_name => "open-ils.actor.container.user.batch_statcat_apply",
802 home_ou => 'UPDATE_USER', # field -> perm means "test this perm with field as context OU", both old and new
804 fields => [ qw/active profile juvenile home_ou expire_date barred net_access_level/ ],
806 desc => 'Edits allowed fields on users in a bucket',
808 desc => 'Session key', type => 'string',
809 desc => 'User container id',
810 desc => 'Hash of statcats to apply or remove', type => 'hash',
813 desc => 'Object with the structure { stage => "stage string", max => max_for_stage, count => count_in_stage }',
825 my $main_fsg = shift;
831 my $class = $self->{ctype} or return undef;
833 my $e = new_editor(xact=>1, authtoken=>$ses);
834 return $e->die_event unless $e->checkauth;
836 for my $bp (@{$batch_perm{$class}}) {
837 return { stage => 'COMPLETE' } unless $e->allowed($bp);
840 $client->respond({ ord => $stage++, stage => 'CONTAINER_BATCH_UPDATE_PERM_CHECK' });
841 return $e->die_event unless $e->allowed('CONTAINER_BATCH_UPDATE');
843 my $meth = 'retrieve_' . $ctypes{$class};
844 my $bkt = $e->$meth($c_id) or return $e->die_event;
846 unless($bkt->owner eq $e->requestor->id) {
847 $client->respond({ ord => $stage++, stage => 'CONTAINER_PERM_CHECK' });
848 my $owner = $e->retrieve_actor_user($bkt->owner)
849 or return $e->die_event;
850 return $e->die_event unless (
851 $e->allowed('VIEW_CONTAINER', $bkt->owning_lib) || $e->allowed('VIEW_CONTAINER', $owner->home_ou)
855 $main_fsg = $e->retrieve_action_fieldset_group($main_fsg);
856 return { stage => 'COMPLETE', error => 'No field set group' } unless $main_fsg;
858 my $rbg = $e->retrieve_action_fieldset_group($main_fsg->rollback_group);
859 return { stage => 'COMPLETE', error => 'No rollback field set group' } unless $rbg;
861 my $fieldsets = $e->search_action_fieldset({fieldset_group => $rbg->id});
862 $max = scalar(@$fieldsets);
864 $client->respond({ ord => $stage, max => $max, count => 0, stage => 'APPLY_EDITS' });
865 for my $fs (@$fieldsets) {
866 my $res = $e->json_query({
867 from => ['action.apply_fieldset', $fs->id, $table{$class}, 'id', undef]
868 })->[0]->{'action.apply_fieldset'};
874 stage => 'APPLY_EDITS',
875 error => $res ? "Could not apply fieldset ".$fs->id.": $res" : undef
879 $main_fsg->rollback_time('now');
880 $e->update_action_fieldset_group($main_fsg);
884 return { stage => 'COMPLETE' };
886 __PACKAGE__->register_method(
887 method => "apply_rollback",
888 max_bundle_count => 1,
889 api_name => "open-ils.actor.container.user.apply_rollback",
892 desc => 'Applys rollback of a fieldset group to users in a bucket',
894 { desc => 'Session key', type => 'string' },
895 { desc => 'User container id', type => 'number' },
896 { desc => 'Main (non-rollback) fieldset group' },
899 desc => 'Object with the structure { fieldset_group => $id, stage => "COMPLETE", error => ("error string if any"|undef if none) }',
911 my $edit_name = shift;
918 my $class = $self->{ctype} or return undef;
920 my $e = new_editor(xact=>1, authtoken=>$ses);
921 return $e->die_event unless $e->checkauth;
923 for my $bp (@{$batch_perm{$class}}) {
924 return { stage => 'COMPLETE' } unless $e->allowed($bp);
927 $client->respond({ ord => $stage++, stage => 'CONTAINER_BATCH_UPDATE_PERM_CHECK' });
928 return $e->die_event unless $e->allowed('CONTAINER_BATCH_UPDATE');
930 my $meth = 'retrieve_' . $ctypes{$class};
931 my $bkt = $e->$meth($c_id) or return $e->die_event;
933 unless($bkt->owner eq $e->requestor->id) {
934 $client->respond({ ord => $stage++, stage => 'CONTAINER_PERM_CHECK' });
935 my $owner = $e->retrieve_actor_user($bkt->owner)
936 or return $e->die_event;
937 return $e->die_event unless (
938 $e->allowed('VIEW_CONTAINER', $bkt->owning_lib) || $e->allowed('VIEW_CONTAINER', $owner->home_ou)
942 $meth = 'search_' . $ctypes{$class} . '_item';
943 my $contents = $e->$meth({bucket => $c_id});
946 $max = scalar(@$contents) if ($self->{perms});
947 $max += scalar(@$contents) if ($self->{base_perm});
950 if ($self->{base_perm}) {
951 $client->respond({ ord => $stage, max => $max, count => $count, stage => 'ITEM_PERM_CHECK' });
952 for my $item (@$contents) {
954 $meth = 'retrieve_' . $itypes{$class};
955 my $field = 'target_'.$ttypes{$class};
956 my $obj = $$obj_cache{$item->$field} = $e->$meth($item->$field);
958 for my $perm_field (keys %{$self->{base_perm}}) {
959 my $perm_def = $self->{base_perm}->{$perm_field};
960 my ($pwhat,$pwhere) = ([split ' ', $perm_def], $perm_field);
961 for my $p (@$pwhat) {
962 $e->allowed($p, $obj->$pwhere) or return $e->die_event;
963 if ($$edits{$pwhere}) {
964 $e->allowed($p, $$edits{$pwhere}) or do {
965 $logger->warn("Cannot update $class ".$obj->id.", $pwhat at $pwhere not allowed.");
966 return $e->die_event;
971 $client->respond({ ord => $stage, max => $max, count => $count, stage => 'ITEM_PERM_CHECK' });
975 if ($self->{perms}) {
976 $client->respond({ ord => $stage, max => $max, count => $count, stage => 'ITEM_PERM_CHECK' });
977 for my $item (@$contents) {
979 $meth = 'retrieve_' . $itypes{$class};
980 my $field = 'target_'.$ttypes{$class};
981 my $obj = $$obj_cache{$item->$field} || $e->$meth($item->$field);
983 for my $perm_field (keys %{$self->{perms}}) {
984 my $perm_def = $self->{perms}->{$perm_field};
985 if (ref($perm_def) eq 'HASH') { # we care about specific values being set
986 for my $perm_value (keys %$perm_def) {
987 if (exists $$edits{$perm_field} && $$edits{$perm_field} eq $perm_value) { # check permission
988 while (my ($pwhat,$pwhere) = each %{$$perm_def{$perm_value}}) {
989 if ($pwhere eq '*') {
992 $pwhere = $obj->$pwhere;
994 $pwhat = [ split / /, $pwhat ];
995 for my $p (@$pwhat) {
996 $e->allowed($p, $pwhere) or do {
997 $pwhere ||= "everywhere";
998 $logger->warn("Cannot update $class ".$obj->id.", $pwhat at $pwhere not allowed.");
999 return $e->die_event;
1005 } elsif (ref($perm_def) eq 'CODE') { # we need to run the code on old and new, and pass both tests
1006 if (exists $$edits{$perm_field}) {
1007 $perm_def->($e, $obj->$perm_field) or return $e->die_event;
1008 $perm_def->($e, $$edits{$perm_field}) or return $e->die_event;
1010 } else { # we're checking an ou field
1011 my ($pwhat,$pwhere) = ([split ' ', $perm_def], $perm_field);
1012 if ($$edits{$pwhere}) {
1013 for my $p (@$pwhat) {
1014 $e->allowed($p, $obj->$pwhere) or return $e->die_event;
1015 $e->allowed($p, $$edits{$pwhere}) or do {
1016 $logger->warn("Cannot update $class ".$obj->id.", $pwhat at $pwhere not allowed.");
1017 return $e->die_event;
1023 $client->respond({ ord => $stage, max => $max, count => $count, stage => 'ITEM_PERM_CHECK' });
1028 $client->respond({ ord => $stage++, stage => 'FIELDSET_GROUP_CREATE' });
1029 my $fsgroup = Fieldmapper::action::fieldset_group->new;
1031 $fsgroup->name($edit_name);
1032 $fsgroup->creator($e->requestor->id);
1033 $fsgroup->owning_lib($e->requestor->ws_ou);
1034 $fsgroup->container($c_id);
1035 $fsgroup->container_type($ttypes{$class});
1036 $fsgroup = $e->create_action_fieldset_group($fsgroup);
1038 $client->respond({ ord => $stage++, stage => 'FIELDSET_CREATE' });
1039 my $fieldset = Fieldmapper::action::fieldset->new;
1040 $fieldset->isnew(1);
1041 $fieldset->fieldset_group($fsgroup->id);
1042 $fieldset->owner($e->requestor->id);
1043 $fieldset->owning_lib($e->requestor->ws_ou);
1044 $fieldset->status('PENDING');
1045 $fieldset->classname($htypes{$class});
1046 $fieldset->name($edit_name . ' batch group fieldset');
1047 $fieldset->stored_query($qtypes{$class});
1048 $fieldset = $e->create_action_fieldset($fieldset);
1050 my @keys = keys %$edits;
1051 $max = scalar(@keys);
1053 $client->respond({ ord => $stage, count=> $count, max => $max, stage => 'FIELDSET_EDITS_CREATE' });
1054 for my $key (@keys) {
1055 if ($self->{fields}) { # restrict edits to registered fields
1056 next unless (grep { $_ eq $key } @{$self->{fields}});
1058 my $fs_cv = Fieldmapper::action::fieldset_col_val->new;
1060 $fs_cv->fieldset($fieldset->id);
1062 $fs_cv->val($$edits{$key});
1063 $e->create_action_fieldset_col_val($fs_cv);
1065 $client->respond({ ord => $stage, count=> $count, max => $max, stage => 'FIELDSET_EDITS_CREATE' });
1068 $client->respond({ ord => ++$stage, stage => 'CONSTRUCT_QUERY' });
1069 my $qstore = OpenSRF::AppSession->connect('open-ils.qstore');
1070 my $prep = $qstore->request('open-ils.qstore.prepare', $fieldset->stored_query)->gather(1);
1071 my $token = $prep->{token};
1072 $qstore->request('open-ils.qstore.bind_param', $token, {bucket => $c_id})->gather(1);
1073 my $sql = $qstore->request('open-ils.qstore.sql', $token)->gather(1);
1074 $sql =~ s/\n\s*/ /g; # normalize the string
1075 $sql =~ s/;\s*//g; # kill trailing semicolon
1077 $client->respond({ ord => ++$stage, stage => 'APPLY_EDITS' });
1078 my $res = $e->json_query({
1079 from => ['action.apply_fieldset', $fieldset->id, $table{$class}, 'id', $sql]
1080 })->[0]->{'action.apply_fieldset'};
1083 $qstore->disconnect;
1085 return { fieldset_group => $fsgroup->id, stage => 'COMPLETE', error => $res };
1088 __PACKAGE__->register_method(
1089 method => "batch_edit",
1090 max_bundle_count => 1,
1091 api_name => "open-ils.actor.container.user.batch_edit",
1093 base_perm => { home_ou => 'UPDATE_USER' },
1096 my ($e, $group) = @_;
1097 my $g = $e->retrieve_permission_grp_tree($group);
1098 if (my $p = $g->application_perm()) {
1099 return $e->allowed($p);
1102 }, # code ref is run with params (editor,value), for both old and new value
1103 # home_ou => 'UPDATE_USER', # field -> perm means "test this perm with field as context OU", both old and new
1105 t => { BAR_PATRON => 'home_ou' },
1106 f => { UNBAR_PATRON => 'home_ou' }
1107 } # field -> struct means "if field getting value "key" check -> perm -> at context org, both old and new
1109 fields => [ qw/active profile juvenile home_ou expire_date barred net_access_level/ ],
1111 desc => 'Edits allowed fields on users in a bucket',
1113 { desc => 'Session key', type => 'string' },
1114 { desc => 'User container id', type => 'number' },
1115 { desc => 'Batch edit name', type => 'string' },
1116 { desc => 'Edit hash, key is column, value is new value to apply', type => 'hash' },
1119 desc => 'Object with the structure { fieldset_group => $id, stage => "COMPLETE", error => ("error string if any"|undef if none) }',
1125 __PACKAGE__->register_method(
1126 method => "batch_edit",
1127 api_name => "open-ils.actor.container.user.batch_delete",
1131 t => { 'DELETE_USER UPDATE_USER' => 'home_ou' },
1132 f => { 'UPDATE_USER' => 'home_ou' }
1135 fields => [ qw/deleted/ ],
1137 desc => 'Deletes users in a bucket',
1139 { desc => 'Session key', type => 'string' },
1140 { desc => 'User container id', type => 'number' },
1141 { desc => 'Batch delete name', type => 'string' },
1142 { desc => 'Edit delete, key is "deleted", value is new value to apply ("t")', type => 'hash' },
1146 desc => 'Object with the structure { fieldset_group => $id, stage => "COMPLETE", error => ("error string if any"|undef if none) }',