From 14e2e47d5a93a380d7249d4033662f68a51c345d Mon Sep 17 00:00:00 2001 From: Mike Rylander Date: Wed, 4 Nov 2015 16:27:04 -0500 Subject: [PATCH] biz logic for alerts Signed-off-by: Mike Rylander --- .../perlmods/lib/OpenILS/Application/AppUtils.pm | 8 + .../lib/OpenILS/Application/Circ/Circulate.pm | 221 ++++++++++++++++++++- .../web/js/ui/default/staff/circ/services/circ.js | 30 ++- 3 files changed, 238 insertions(+), 21 deletions(-) diff --git a/Open-ILS/src/perlmods/lib/OpenILS/Application/AppUtils.pm b/Open-ILS/src/perlmods/lib/OpenILS/Application/AppUtils.pm index a8661cc851..1e984fb113 100644 --- a/Open-ILS/src/perlmods/lib/OpenILS/Application/AppUtils.pm +++ b/Open-ILS/src/perlmods/lib/OpenILS/Application/AppUtils.pm @@ -2279,6 +2279,14 @@ sub unique_unnested_numbers { ); } +# Given a list of numbers, turn them into a PG array, skipping undef's +sub intarray2pgarray { + my $class = shift; + no warnings 'numeric'; + + return '{' . join( ',', map(int, grep { defined && /^\d+$/ } @_) ) . '}'; +} + # Check if a transaction should be left open or closed. Close the # transaction if it should be closed or open it otherwise. Returns # undef on success or a failure event. diff --git a/Open-ILS/src/perlmods/lib/OpenILS/Application/Circ/Circulate.pm b/Open-ILS/src/perlmods/lib/OpenILS/Application/Circ/Circulate.pm index ce05bc0c2f..cb124fe37b 100644 --- a/Open-ILS/src/perlmods/lib/OpenILS/Application/Circ/Circulate.pm +++ b/Open-ILS/src/perlmods/lib/OpenILS/Application/Circ/Circulate.pm @@ -155,6 +155,7 @@ sub run_method { my( $self, $conn, $auth, $args ) = @_; translate_legacy_args($args); $args->{override_args} = { all => 1 } unless defined $args->{override_args}; + $args->{new_copy_alerts} ||= $self->api_level > 1 ? 1 : 0; my $api = $self->api_name; my $circulator = @@ -227,13 +228,13 @@ sub run_method { $circulator->is_renewal(1) if $api =~ /renew/; $circulator->is_checkin(1) if $api =~ /checkin/; + $circulator->is_checkout(1) if $api =~ /checkout/; + $circulator->override(1) if $api =~ /override/o; $circulator->mk_env(); $circulator->noop(1) if $circulator->claims_never_checked_out; return circ_events($circulator) if $circulator->bail_out; - - $circulator->override(1) if $api =~ /override/o; if( $api =~ /checkout\.permit/ ) { $circulator->do_permit(); @@ -260,7 +261,6 @@ sub run_method { return $data; } elsif( $api =~ /checkout/ ) { - $circulator->is_checkout(1); $circulator->do_checkout(); } elsif( $circulator->is_res_checkin ) { @@ -270,7 +270,6 @@ sub run_method { $circulator->do_checkin(); } elsif( $api =~ /renew/ ) { - $circulator->is_renewal(1); $circulator->do_renew(); } @@ -404,6 +403,10 @@ my @AUTOLOAD_FIELDS = qw/ copy copy_id copy_barcode + user_copy_alerts + system_copy_alerts + next_copy_status + copy_state patron patron_id patron_barcode @@ -640,10 +643,174 @@ sub save_trimmed_copy { } } +sub collect_user_copy_alerts { + my $self = shift; + my $e = $self->editor; + + if($self->copy) { + my $alerts = $e->search_asset_copy_alert([ + {copy => $self->copy->id, ack_time => undef} + {flesh => 1, flesh_fields => { ccat => [ qw/ alert_type / ] }} + ]); + if (ref $alerts eq "ARRAY") { + $logger->info("circulator: found " . scalar(@$alerts) . " alerts for copy" . + $self->copy->id); + $self->user_copy_alerts($alerts); + } + } +} + +sub filter_user_copy_alerts { + my $self = shift; + + if(my $alerts = $self->user_copy_alerts) { + + my $suppress_orgs = $U->get_org_descendants($self->circ_lib); + my $suppressions = $e->search_actor_copy_alert_suppress( + {org => $suppress_orgs} + ); + + my @final_alerts; + foreach my $a (@$alerts) { + # filter on event type + if (defined $a->alert_type) { + next if ($a->alert_type->event eq 'CHECKIN' && !$self->is_checkin); + next if ($a->alert_type->event eq 'CHECKOUT' && !$self->is_checkout); + next if (defined $a->alert_type->in_renew && $U->is_true($a->in_renew) && !$self->is_renewal); + } + + # filter on suppression + next if (grep { $a->alert_type->id == $_->alert_type} @$suppressions); + + # filter on "only at circ lib" + if (defined $a->alert_type->at_circ) { + my $copy_circ_lib = (ref $self->copy->circ_lib) ? + $self->copy->circ_lib->id : $self->copy->circ_lib; + my $orgs = $U->get_org_descendants($copy_circ_lib); + + if ($U->is_true($a->alert_type->invert_location)) { + next if (grep {$_ == $copy_circ_lib} @$orgs); + } else { + next unless (grep {$_ == $copy_circ_lib} @$orgs); + } + } + + # filter on "only at owning lib" + if (defined $a->alert_type->at_owning) { + my $copy_owning_lib = (ref $self->volume->owning_lib) ? + $self->volume->owning_lib->id : $self->volume->owning_lib; + my $orgs = $U->get_org_descendants($copy_owning_lib); + + if ($U->is_true($a-->alert_type>invert_location)) { + next if (grep {$_ == $copy_owning_lib} @$orgs); + } else { + next unless (grep {$_ == $copy_owning_lib} @$orgs); + } + } + + $a->alert_type->next_status($U->unique_unnested_numbers($a->alert_type->next_status)); + + push @final_alerts, $a; + } + + $self->user_copy_alerts(\@final_alerts); + } +} + +sub generate_system_copy_alerts { + my $self = shift; + return unless($self->copy); + + my $e = $self->editor; + + my $suppress_orgs = $U->get_org_descendants($self->circ_lib); + my $suppressions = $e->search_actor_copy_alert_suppress( + {org => $suppress_orgs} + ); + + # events we care about ... + my $event = []; + push(@$event, 'CHECKIN') if $self->is_checkin; + push(@$event, 'CHECKOUT') if $self->is_checkout; + return unless scalar(@$event); + + my $alert_orgs = $U->get_org_ancestors($self->circ_lib); + my $alert_types = $e->search_config_copy_alert_type({ + active => 't' + scope_org => $alert_orgs, + event => $event, + state => $self->copy_state, + '-or' => [ { in_renew => $self->is_renewal }, { in_renew => undef } ], + }); + + my @final_types; + foreach my $a (@$alert_types) { + # filter on "only at circ lib" + if (defined $a->at_circ) { + my $copy_circ_lib = (ref $self->copy->circ_lib) ? + $self->copy->circ_lib->id : $self->copy->circ_lib; + my $orgs = $U->get_org_descendants($copy_circ_lib); + + if ($U->is_true($a->invert_location)) { + next if (grep {$_ == $copy_circ_lib} @$orgs); + } else { + next unless (grep {$_ == $copy_circ_lib} @$orgs); + } + } + + # filter on "only at owning lib" + if (defined $a->at_owning) { + my $copy_owning_lib = (ref $self->volume->owning_lib) ? + $self->volume->owning_lib->id : $self->volume->owning_lib; + my $orgs = $U->get_org_descendants($copy_owning_lib); + + if ($U->is_true($a->invert_location)) { + next if (grep {$_ == $copy_owning_lib} @$orgs); + } else { + next unless (grep {$_ == $copy_owning_lib} @$orgs); + } + } + + push @final_types, $a; + } + + if (@final_types) { + $logger->info("circulator: found " . scalar(@final_types) . " system alert types for copy" . + $self->copy->id); + } + + my @alerts; + foreach my $t (@final_types) { + $t->next_status($U->unique_unnested_numbers($t->next_status)); + + my $alert = new Fieldmapper::asset::copy_alert; + $alert->alert_type($t->id); + $alert->copy($self->copy->id); + $alert->temp(1); + $alert->create_staff($e->requestor->id); + $alert->create_time('now'); + $alert->ack_staff($e->requestor->id); + $alert->ack_time('now'); + + $alert = $e->create_asset_copy_alert($alert); + + next unless $alert; + + $alert->alert_type($t->clone); + + push(@{$self->next_copy_status}, $t->next_status) if ($t->next_status); + push(@alerts, $alert) unless (grep {$_->alert_type == $t->id} @$suppressions); + } + + $self->system_copy_alerts(\@alerts); +} + sub mk_env { my $self = shift; my $e = $self->editor; + $self->next_copy_status([]) unless (defined $self->next_copy_status); + # -------------------------------------------------------------------------- # Grab the fleshed copy # -------------------------------------------------------------------------- @@ -676,6 +843,7 @@ sub mk_env { } }, "where" => { + deleted => 'f', "+bresv" => { "id" => (ref $self->reservation) ? $self->reservation->id : $self->reservation @@ -692,6 +860,18 @@ sub mk_env { if($copy) { $self->save_trimmed_copy($copy); + + # alerts! + $self->copy_state( + $e->json_query( + {from => ['asset.copy_state', $copy->id]} + )->[0]{'asset.copy_state'} + ); + + $self->generate_system_copy_alerts; + $self->collect_user_copy_alerts; + $self->filter_user_copy_alerts; + } else { # We can't renew if there is no copy return $self->bail_on_events(OpenILS::Event->new('ASSET_COPY_NOT_FOUND')) @@ -1207,6 +1387,21 @@ sub run_copy_permit_scripts { sub check_copy_alert { my $self = shift; + + if ($self->new_copy_alerts) { + my @alerts; + push @alerts, @{$self->user_copy_alerts}) # we have preexisting alerts + if ($self->user_copy_alerts && @{$self->user_copy_alerts}); + + push @alerts, @{$self->system_copy_alerts}) # we have new dynamic alerts + if ($self->system_copy_alerts && @{$self->system_copy_alerts}); + + if (@alerts) { + $self->bail_out(1) if (!$self->override); + return OpenILS::Event->new( 'COPY_ALERT_MESSAGE', payload => \@alerts); + } + } + return undef if $self->is_renewal; return OpenILS::Event->new( 'COPY_ALERT_MESSAGE', payload => $self->copy->alert_message) @@ -2640,7 +2835,8 @@ sub do_checkin { $U->ou_ancestor_setting_value($self->circ->circ_lib, 'circ.claim_never_checked_out.mark_missing')) { # the item was not supposed to be checked out to the user and should now be marked as missing - $self->copy->status(OILS_COPY_STATUS_MISSING); + my $next_status = $self->next_copy_status->[0] || OILS_COPY_STATUS_MISSING; + $self->copy->status($next_status); $self->update_copy; } else { @@ -2716,13 +2912,15 @@ sub reshelve_copy { my $stat = $U->copy_status($copy->status)->id; + my $next_status = $self->next_copy_status->[0] || OILS_COPY_STATUS_RESHELVING; + if($force || ( $stat != OILS_COPY_STATUS_ON_HOLDS_SHELF and $stat != OILS_COPY_STATUS_CATALOGING and $stat != OILS_COPY_STATUS_IN_TRANSIT and - $stat != OILS_COPY_STATUS_RESHELVING )) { + $stat != $next_status )) { - $copy->status( OILS_COPY_STATUS_RESHELVING ); + $copy->status( $next_status ); $self->update_copy; $self->checkin_changed(1); } @@ -3333,7 +3531,8 @@ sub checkin_handle_circ_start { } elsif ($circ_lib != $self->circ_lib and $stat == OILS_COPY_STATUS_MISSING) { $logger->info("circulator: not updating copy status on checkin because copy is missing"); } else { - $self->copy->status($U->copy_status(OILS_COPY_STATUS_RESHELVING)); + my $next_status = $self->next_copy_status->[0] || OILS_COPY_STATUS_RESHELVING; + $self->copy->status($U->copy_status($next_status)); $self->update_copy; } @@ -3521,7 +3720,8 @@ sub checkin_handle_lost_or_longoverdue { if ($immediately_available) { # item status does not need to be retained, so give it a # reshelving status as if it were a normal checkin - $self->copy->status($U->copy_status(OILS_COPY_STATUS_RESHELVING)); + my $next_status = $self->next_copy_status->[0] || OILS_COPY_STATUS_RESHELVING; + $self->copy->status($U->copy_status($next_status)); $self->update_copy; } else { $logger->info("circulator: leaving lost/longoverdue copy". @@ -3530,7 +3730,8 @@ sub checkin_handle_lost_or_longoverdue { } else { # lost/longoverdue item is home and processed, treat like a normal # checkin from this point on - $self->copy->status($U->copy_status(OILS_COPY_STATUS_RESHELVING)); + my $next_status = $self->next_copy_status->[0] || OILS_COPY_STATUS_RESHELVING; + $self->copy->status($U->copy_status($next_status)); $self->update_copy; } } diff --git a/Open-ILS/web/js/ui/default/staff/circ/services/circ.js b/Open-ILS/web/js/ui/default/staff/circ/services/circ.js index 27755fe3b5..27b55086d0 100644 --- a/Open-ILS/web/js/ui/default/staff/circ/services/circ.js +++ b/Open-ILS/web/js/ui/default/staff/circ/services/circ.js @@ -122,6 +122,8 @@ function($uibModal , $q , egCore , egAlertDialog , egConfirmDialog, // options : non-parameter controls. e.g. "override", "check_barcode" service.checkout = function(params, options) { if (!options) options = {}; + params.new_copy_alerts = 1; + console.debug('egCirc.checkout() : ' + js2JSON(params) + ' : ' + js2JSON(options)); @@ -176,6 +178,7 @@ function($uibModal , $q , egCore , egAlertDialog , egConfirmDialog, // Rejected if the renewal cannot be completed. service.renew = function(params, options) { if (!options) options = {}; + params.new_copy_alerts = 1; console.debug('egCirc.renew() : ' + js2JSON(params) + ' : ' + js2JSON(options)); @@ -223,6 +226,7 @@ function($uibModal , $q , egCore , egAlertDialog , egConfirmDialog, // Rejected if the checkin cannot be completed. service.checkin = function(params, options) { if (!options) options = {}; + params.new_copy_alerts = 1; console.debug('egCirc.checkin() : ' + js2JSON(params) + ' : ' + js2JSON(options)); @@ -1532,17 +1536,21 @@ function($uibModal , $q , egCore , egAlertDialog , egConfirmDialog, // action == what action to take if the user confirms the alert service.copy_alert_dialog = function(evt, params, options, action) { if (angular.isArray(evt)) evt = evt[0]; - return egConfirmDialog.open( - egCore.strings.COPY_ALERT_MSG_DIALOG_TITLE, - evt.payload, // payload == alert message text - { copy_barcode : params.copy_barcode, - ok : function() {}, - cancel : function() {} - } - ).result.then(function() { - options.override = true; - return service[action](params, options); - }); + if (!angular.isArray(evt.payload)) { + return egConfirmDialog.open( + egCore.strings.COPY_ALERT_MSG_DIALOG_TITLE, + evt.payload, // payload == alert message text + { copy_barcode : params.copy_barcode, + ok : function() {}, + cancel : function() {} + } + ).result.then(function() { + options.override = true; + return service[action](params, options); + }); + } else { // we got a list of copy alert objects ... + // TODO: build a chain of alert ack (and maybe status selection) dialogs + } } // action == what action to take if the user confirms the alert -- 2.11.0