From: Jason Stephenson <jason@sigio.com> Date: Sat, 27 Oct 2018 19:48:54 +0000 (-0400) Subject: LP 1779467: Enhance Mark Items Functionality X-Git-Url: https://old-git.evergreen-ils.org/?a=commitdiff_plain;h=cf88a1bfba5d9476388cea8d763b96455a0d3bb6;p=evergreen%2Fequinox.git LP 1779467: Enhance Mark Items Functionality A new option to "Mark Item as Discard/Weed" is added to many actions menus in the staff client. This command is connected to the back end function open-ils.circ.mark_item_discard. The back end functionality for the open-ils.circ.mark_item_* family of functions is altered to provide more consistent behavior and to avoid some strange situations that have come up in the past, such as items with the Missing status having active transits or open circulations. The code for "Mark Item as Damaged" and "Mark Item as Missing" are altered to take advantage of the back end changes. NB: These changes do not affect the "Mark Item as Missing Pieces" function, as that is handled by different back end code. Perl live tests are added for the backend functionality changes to test that certain conditions works. Like most of our tests these could be expanded to cover more potential situations. See the release notes for more detail on changes in functionality. Signed-off-by: Jason Stephenson <jason@sigio.com> Signed-off-by: Kathy Lussier <klussier@masslnc.org> Signed-off-by: Terran McCanna <tmccanna@georgialibraries.org> Signed-off-by: Chris Sharp <csharp@georgialibraries.org> --- diff --git a/Open-ILS/src/extras/ils_events.xml b/Open-ILS/src/extras/ils_events.xml index 9f92944cc5..362fff563d 100644 --- a/Open-ILS/src/extras/ils_events.xml +++ b/Open-ILS/src/extras/ils_events.xml @@ -1060,6 +1060,18 @@ <event code='11010' textcode='SERIAL_CAPTION_AND_PATTERN_NOT_EMPTY'> <desc xml:lang="en-US">The prediction pattern still has dependent objects</desc> </event> + + <!-- ================================================================ --> + + <event code='12000' textcode='ITEM_TO_MARK_CHECKED_OUT'> + <desc xml:lang="en-US">The item to be marked is checked out to a patron.</desc> + </event> + <event code='12001' textcode='ITEM_TO_MARK_IN_TRANSIT'> + <desc xml:lang="en-US">The item to be marked is in transit.</desc> + </event> + <event code='12002' textcode='ITEM_TO_MARK_LAST_HOLD_COPY'> + <desc xml:lang="en-US">The item to be marked is the last possible target for a hold.</desc> + </event> </ils_events> diff --git a/Open-ILS/src/perlmods/lib/OpenILS/Application/Circ.pm b/Open-ILS/src/perlmods/lib/OpenILS/Application/Circ.pm index bad3be3819..f3cec222d0 100644 --- a/Open-ILS/src/perlmods/lib/OpenILS/Application/Circ.pm +++ b/Open-ILS/src/perlmods/lib/OpenILS/Application/Circ.pm @@ -1307,31 +1307,24 @@ sub mark_item { my( $self, $conn, $auth, $copy_id, $args ) = @_; $args ||= {}; - # Items must be checked in before any attempt is made to mark damaged - my $evt = try_checkin($auth, $copy_id) if - ($self->api_name=~ /damaged/ && $args->{handle_checkin}); - return $evt if $evt; - - my $e = new_editor(authtoken=>$auth, xact =>1); + my $e = new_editor(authtoken=>$auth); return $e->die_event unless $e->checkauth; my $copy = $e->retrieve_asset_copy([ $copy_id, - {flesh => 1, flesh_fields => {'acp' => ['call_number']}}]) + {flesh => 1, flesh_fields => {'acp' => ['call_number','status']}}]) or return $e->die_event; - my $owning_lib = + my $owning_lib = ($copy->call_number->id == OILS_PRECAT_CALL_NUMBER) ? $copy->circ_lib : $copy->call_number->owning_lib; + my $evt; # For later. my $perm = 'MARK_ITEM_MISSING'; my $stat = OILS_COPY_STATUS_MISSING; if( $self->api_name =~ /damaged/ ) { $perm = 'MARK_ITEM_DAMAGED'; $stat = OILS_COPY_STATUS_DAMAGED; - my $evt = handle_mark_damaged($e, $copy, $owning_lib, $args); - return $evt if $evt; - } elsif ( $self->api_name =~ /bindery/ ) { $perm = 'MARK_ITEM_BINDERY'; $stat = OILS_COPY_STATUS_BINDERY; @@ -1355,20 +1348,73 @@ sub mark_item { # caller may proceed if either perm is allowed return $e->die_event unless $e->allowed([$perm, 'UPDATE_COPY'], $owning_lib); - $copy->status($stat); - $copy->edit_date('now'); - $copy->editor($e->requestor->id); - - $e->update_asset_copy($copy) or return $e->die_event; + # Copy status checks. + if ($copy->status->id() == OILS_COPY_STATUS_CHECKED_OUT) { + # Items must be checked in before any attempt is made to change its status. + if ($args->{handle_checkin}) { + $evt = try_checkin($auth, $copy_id); + } else { + $evt = OpenILS::Event->new('ITEM_TO_MARK_CHECKED_OUT'); + } + } elsif ($copy->status->id() == OILS_COPY_STATUS_IN_TRANSIT) { + # Items in transit need to have the transit aborted before being marked. + if ($args->{handle_transit}) { + $evt = try_abort_transit($auth, $copy_id); + } else { + $evt = OpenILS::Event->new('ITEM_TO_MARK_IN_TRANSIT'); + } + } elsif ($U->is_true($copy->status->restrict_copy_delete()) && $self->api_name =~ /discard/) { + # Items with restrict_copy_delete status require the + # COPY_DELETE_WARNING.override permission to be marked for + # discard. + if ($args->{handle_copy_delete_warning}) { + $evt = $e->event unless $e->allowed(['COPY_DELETE_WARNING.override'], $owning_lib); + } else { + $evt = OpenILS::Event->new('COPY_DELETE_WARNING'); + } + } + return $evt if $evt; + # Retrieving holds for later use. my $holds = $e->search_action_hold_request( - { + { current_copy => $copy->id, fulfillment_time => undef, cancel_time => undef, - } + }, + {flesh=>1, flesh_fields=>{ahr=>['eligible_copies']}} ); + # Throw event if attempting to mark discard the only copy to fill a hold. + if ($self->api_name =~ /discard/) { + if (!$args->{handle_last_hold_copy}) { + for my $hold (@$holds) { + my $eligible = $hold->eligible_copies(); + if (scalar(@{$eligible}) < 2) { + $evt = OpenILS::Event->new('ITEM_TO_MARK_LAST_HOLD_COPY'); + last; + } + } + } + } + return $evt if $evt; + + # Things below here require a transaction and there is nothing left to interfere with it. + $e->xact_begin; + + # Handle extra mark damaged charges, etc. + if ($self->api_name =~ /damaged/) { + $evt = handle_mark_damaged($e, $copy, $owning_lib, $args); + return $evt if $evt; + } + + # Mark the copy. + $copy->status($stat); + $copy->edit_date('now'); + $copy->editor($e->requestor->id); + + $e->update_asset_copy($copy) or return $e->die_event; + $e->commit; if( $self->api_name =~ /damaged/ ) { @@ -1408,6 +1454,19 @@ sub try_checkin { } } +sub try_abort_transit { + my ($auth, $copy_id) = @_; + + my $abort = $U->simplereq( + 'open-ils.circ', + 'open-ils.circ.transit.abort', + $auth, {copyid => $copy_id} + ); + # Above returns 1 or an event. + return $abort if (ref $abort); + return undef; +} + sub handle_mark_damaged { my($e, $copy, $owning_lib, $args) = @_; diff --git a/Open-ILS/src/perlmods/live_t/zz-lp1779467-mark-item-discard.t b/Open-ILS/src/perlmods/live_t/zz-lp1779467-mark-item-discard.t new file mode 100644 index 0000000000..08f99b1c3d --- /dev/null +++ b/Open-ILS/src/perlmods/live_t/zz-lp1779467-mark-item-discard.t @@ -0,0 +1,238 @@ +#!perl +use strict; use warnings; +use Test::More tests => 17; +use OpenILS::Utils::TestUtils; +use OpenILS::Const qw(:const); + +my $script = OpenILS::Utils::TestUtils->new(); +my $U = 'OpenILS::Application::AppUtils'; + +diag("Test LP 1779467 Enhance Mark Item Discard/Weed."); + +use constant { + BR1_ID => 4, + BR3_ID => 6, + WORKSTATION => 'BR1-lp1779467-test-mark-item-discard' +}; + +# We are deliberately NOT using the admin user to check for a perm failure. +my $credentials = { + username => 'br1mtownsend', + password => 'maryt1234', + type => 'staff' +}; + +# Log in as staff. +my $authtoken = $script->authenticate($credentials); +ok( + $authtoken, + 'Logged in' +) or BAIL_OUT('Must log in'); + +# Find or register workstation. +my $ws = $script->find_or_register_workstation(WORKSTATION, BR1_ID); +ok( + ! ref $ws, + 'Found or registered workstation' +) or BAIL_OUT('Need Workstation'); + +# Logout. +$script->logout(); +ok( + ! $script->authtoken, + 'Logged out' +); + +# Login with workstation. +$credentials->{workstation} = WORKSTATION; +$credentials->{password} = 'maryt1234'; +$authtoken = $script->authenticate($credentials); +ok( + $script->authtoken, + 'Logged in with workstation' +) or BAIL_OUT('Must log in'); + +# Find available copy at BR1 +my $acps = $U->simplereq( + 'open-ils.pcrud', + 'open-ils.pcrud.search.acp.atomic', + $authtoken, + {circ_lib => BR1_ID, status => OILS_COPY_STATUS_AVAILABLE}, + {limit => 1} +); +my $copy = $acps->[0]; +isa_ok( + ref $copy, + 'Fieldmapper::asset::copy', + 'Got available copy from BR1' +); + +# Mark it discard/weed. +my $result = $U->simplereq( + 'open-ils.circ', + 'open-ils.circ.mark_item_discard', + $authtoken, + $copy->id() +); +is( + $result, + 1, + 'Mark available copy Discard/Weed' +); + +# Check its status. +$copy = $U->simplereq( + 'open-ils.pcrud', + 'open-ils.pcrud.retrieve.acp', + $authtoken, + $copy->id() +); +is( + $copy->status(), + OILS_COPY_STATUS_DISCARD, + 'Copy has Discard/Weed status' +); + +# Find available copy at BR3. +$acps = $U->simplereq( + 'open-ils.pcrud', + 'open-ils.pcrud.search.acp.atomic', + $authtoken, + {circ_lib => BR3_ID, status => OILS_COPY_STATUS_AVAILABLE}, + {limit => 1} +); +$copy = $acps->[0]; +isa_ok( + ref $copy, + 'Fieldmapper::asset::copy', + 'Got available copy from BR3' +); + +# Attempt to mark it discard/weed. +# Should fail with a perm error. +$result = $U->simplereq( + 'open-ils.circ', + 'open-ils.circ.mark_item_discard', + $authtoken, + $copy->id() +); +is( + $result->{textcode}, + 'PERM_FAILURE', + 'Mark BR3 copy Discard/Weed' +); + +# Find checked out copy at BR1. +$acps = $U->simplereq( + 'open-ils.pcrud', + 'open-ils.pcrud.search.acp.atomic', + $authtoken, + {circ_lib => BR1_ID, status => OILS_COPY_STATUS_CHECKED_OUT}, + {limit => 1} +); +$copy = $acps->[0]; +isa_ok( + ref $copy, + 'Fieldmapper::asset::copy', + 'Got checked out copy from BR1' +); + +# Mark it discard/weed with handle_checkin: 1. +$result = $U->simplereq( + 'open-ils.circ', + 'open-ils.circ.mark_item_discard', + $authtoken, + $copy->id(), + {handle_checkin => 1} +); +ok( + $result == 1, + 'Mark checked out item discard' +); + +# Check its status. +$copy = $U->simplereq( + 'open-ils.pcrud', + 'open-ils.pcrud.retrieve.acp', + $authtoken, + $copy->id() +); +is( + $copy->status(), + OILS_COPY_STATUS_DISCARD, + 'Checked out copy has Discard/Weed status' +); + +# Check that it is no longer checked out. +my $circ = $U->simplereq( + 'open-ils.pcrud', + 'open-ils.pcrud.search.circ', + $authtoken, + {target_copy => $copy->id(), checkin_time => undef} +); +ok( + ! defined $circ, + 'No circulation for marked copy' +); + +# Find another checked out copy at BR1. +$acps = $U->simplereq( + 'open-ils.pcrud', + 'open-ils.pcrud.search.acp.atomic', + $authtoken, + {circ_lib => BR1_ID, status => OILS_COPY_STATUS_CHECKED_OUT}, + {limit => 1} +); +$copy = $acps->[0]; +isa_ok( + ref $copy, + 'Fieldmapper::asset::copy', + 'Got another checked out copy from BR1' +); + +# Mark it discard/weed with handle_checkin: 0. +$result = $U->simplereq( + 'open-ils.circ', + 'open-ils.circ.mark_item_discard', + $authtoken, + $copy->id(), + {handle_checkin => 0} +); +# Check that we got the appropriate event: ITEM_TO_MARK_CHECKED_OUT +is( + $result->{textcode}, + 'ITEM_TO_MARK_CHECKED_OUT', + 'Mark second checked out item discard' +); + +# Check its status. +$copy = $U->simplereq( + 'open-ils.pcrud', + 'open-ils.pcrud.retrieve.acp', + $authtoken, + $copy->id() +); +is( + $copy->status(), + OILS_COPY_STATUS_CHECKED_OUT, + 'Second checked out copy has Checked Out status' +); + +# Check that it is still checked out. +$circ = $U->simplereq( + 'open-ils.pcrud', + 'open-ils.pcrud.search.circ', + $authtoken, + {target_copy => $copy->id(), checkin_time => undef} +); +isa_ok( + $circ, + 'Fieldmapper::action::circulation', + 'Second copy still has a circulation' +); + +# We could add more tests for other conditions, i.e. a copy in transit +# and for marking other statuses. + +# Logout +$script->logout(); # Not a test, just to be pedantic. diff --git a/Open-ILS/src/templates/staff/cat/catalog/t_holdings.tt2 b/Open-ILS/src/templates/staff/cat/catalog/t_holdings.tt2 index b694d6bc6c..d30ad1ab67 100644 --- a/Open-ILS/src/templates/staff/cat/catalog/t_holdings.tt2 +++ b/Open-ILS/src/templates/staff/cat/catalog/t_holdings.tt2 @@ -62,6 +62,8 @@ <eg-grid-action handler="selectedHoldingsDamaged" group="[% l('Mark') %]" label="[% l('Item as Damaged') %]"></eg-grid-action> + <eg-grid-action handler="selectedHoldingsDiscard" group="[% l('Mark') %]" + label="[% l('Item as Discard/Weed') %]"></eg-grid-action> <eg-grid-action handler="selectedHoldingsMissing" group="[% l('Mark') %]" label="[% l('Item as Missing') %]"></eg-grid-action> <eg-grid-action handler="markFromSelectedAsHoldingsTarget" group="[% l('Mark') %]" diff --git a/Open-ILS/src/templates/staff/cat/catalog/t_holds.tt2 b/Open-ILS/src/templates/staff/cat/catalog/t_holds.tt2 index c39953fdc8..f50be4cd93 100644 --- a/Open-ILS/src/templates/staff/cat/catalog/t_holds.tt2 +++ b/Open-ILS/src/templates/staff/cat/catalog/t_holds.tt2 @@ -49,6 +49,8 @@ label="[% l('Transfer To Marked Title') %]"></eg-grid-action> <eg-grid-action handler="grid_actions.mark_damaged_wide" group="[% l('Item') %]" label="[% l('Mark Item Damaged') %]"></eg-grid-action> + <eg-grid-action handler="grid_actions.mark_discard_wide" group="[% l('Item') %]" + label="[% l('Mark Item Discard/Weed') %]"></eg-grid-action> <eg-grid-action handler="grid_actions.mark_missing_wide" group="[% l('Item') %]" label="[% l('Mark Item Missing') %]"></eg-grid-action> <eg-grid-action handler="grid_actions.retarget_wide" group="[% l('Hold') %]" diff --git a/Open-ILS/src/templates/staff/cat/item/index.tt2 b/Open-ILS/src/templates/staff/cat/item/index.tt2 index c77ced848a..96c402c043 100644 --- a/Open-ILS/src/templates/staff/cat/item/index.tt2 +++ b/Open-ILS/src/templates/staff/cat/item/index.tt2 @@ -101,6 +101,7 @@ <p><b>[% l('Mark') %]</b></p> <li><a href ng-click="selectedHoldingsDamaged()">[% l('Item as Damaged') %]</a></li> + <li><a href ng-click="selectedHoldingsDiscard()">[% l('Item as Discard/Weed') %]</a></li> <li><a href ng-click="selectedHoldingsMissing()">[% l('Item as Missing') %]</a></li> <p><b>[% l('Add') %]</b></p> diff --git a/Open-ILS/src/templates/staff/cat/item/t_list.tt2 b/Open-ILS/src/templates/staff/cat/item/t_list.tt2 index c0ac0c25d5..024271e767 100644 --- a/Open-ILS/src/templates/staff/cat/item/t_list.tt2 +++ b/Open-ILS/src/templates/staff/cat/item/t_list.tt2 @@ -47,6 +47,8 @@ <eg-grid-action handler="selectedHoldingsDamaged" group="[% l('Mark') %]" label="[% l('Item as Damaged') %]"></eg-grid-action> + <eg-grid-action handler="selectedHoldingsDiscard" group="[% l('Mark') %]" + label="[% l('Item as Discard/Weed') %]"></eg-grid-action> <eg-grid-action handler="selectedHoldingsMissing" group="[% l('Mark') %]" label="[% l('Item as Missing') %]"></eg-grid-action> diff --git a/Open-ILS/src/templates/staff/circ/checkin/t_checkin_table.tt2 b/Open-ILS/src/templates/staff/circ/checkin/t_checkin_table.tt2 index eff1448663..85fd44ec97 100644 --- a/Open-ILS/src/templates/staff/circ/checkin/t_checkin_table.tt2 +++ b/Open-ILS/src/templates/staff/circ/checkin/t_checkin_table.tt2 @@ -24,6 +24,10 @@ label="[% l('Mark Items Damaged') %]"> </eg-grid-action> <eg-grid-action + handler="showMarkDiscard" + label="[% l('Mark Items Discard/Weed') %]"> + </eg-grid-action> + <eg-grid-action handler="show_mark_missing_pieces" label="[% l('Mark Missing Pieces') %]"> </eg-grid-action> diff --git a/Open-ILS/src/templates/staff/circ/holds/t_pull_list.tt2 b/Open-ILS/src/templates/staff/circ/holds/t_pull_list.tt2 index 4bd3762cab..2348f992cd 100644 --- a/Open-ILS/src/templates/staff/circ/holds/t_pull_list.tt2 +++ b/Open-ILS/src/templates/staff/circ/holds/t_pull_list.tt2 @@ -45,6 +45,8 @@ label="[% l('Transfer To Marked Title') %]"></eg-grid-action> <eg-grid-action handler="grid_actions.mark_damaged" label="[% l('Mark Item Damaged') %]"></eg-grid-action> + <eg-grid-action handler="grid_actions.mark_discard" + label="[% l('Mark Item Discard/Weed') %]"></eg-grid-action> <eg-grid-action handler="grid_actions.mark_missing" label="[% l('Mark Item Missing') %]"></eg-grid-action> <eg-grid-action divider="true"></eg-grid-action> diff --git a/Open-ILS/src/templates/staff/circ/holds/t_shelf_list.tt2 b/Open-ILS/src/templates/staff/circ/holds/t_shelf_list.tt2 index 083edec8be..c385a6f8a2 100644 --- a/Open-ILS/src/templates/staff/circ/holds/t_shelf_list.tt2 +++ b/Open-ILS/src/templates/staff/circ/holds/t_shelf_list.tt2 @@ -48,6 +48,8 @@ label="[% l('Transfer To Marked Title') %]"></eg-grid-action> <eg-grid-action handler="grid_actions.mark_damaged_wide" label="[% l('Mark Item Damaged') %]"></eg-grid-action> + <eg-grid-action handler="grid_actions.mark_discard_wide" + label="[% l('Mark Item Discard/Weed') %]"></eg-grid-action> <eg-grid-action handler="grid_actions.mark_missing_wide" label="[% l('Mark Item Missing') %]"></eg-grid-action> <eg-grid-action divider="true"></eg-grid-action> diff --git a/Open-ILS/src/templates/staff/circ/patron/t_holds_list.tt2 b/Open-ILS/src/templates/staff/circ/patron/t_holds_list.tt2 index 14a6569653..48700fcc22 100644 --- a/Open-ILS/src/templates/staff/circ/patron/t_holds_list.tt2 +++ b/Open-ILS/src/templates/staff/circ/patron/t_holds_list.tt2 @@ -35,6 +35,8 @@ label="[% l('Transfer To Marked Title') %]"></eg-grid-action> <eg-grid-action handler="grid_actions.mark_damaged" label="[% l('Mark Item Damaged') %]"></eg-grid-action> + <eg-grid-action handler="grid_actions.mark_discard" + label="[% l('Mark Item Discard/Weed') %]"></eg-grid-action> <eg-grid-action handler="grid_actions.mark_missing" label="[% l('Mark Item Missing') %]"></eg-grid-action> <eg-grid-action divider="true"></eg-grid-action> diff --git a/Open-ILS/src/templates/staff/circ/renew/t_renew.tt2 b/Open-ILS/src/templates/staff/circ/renew/t_renew.tt2 index 886e93fdbb..209570973d 100644 --- a/Open-ILS/src/templates/staff/circ/renew/t_renew.tt2 +++ b/Open-ILS/src/templates/staff/circ/renew/t_renew.tt2 @@ -61,6 +61,10 @@ handler="showMarkDamaged" label="[% l('Mark Items Damaged') %]"> </eg-grid-action> + <eg-grid-action + handler="showMarkDiscard" + label="[% l('Mark Items Discard/Weed') %]"> + </eg-grid-action> <eg-grid-action divider="true"></eg-grid-action> <eg-grid-action handler="abortTransit" diff --git a/Open-ILS/src/templates/staff/circ/share/circ_strings.tt2 b/Open-ILS/src/templates/staff/circ/share/circ_strings.tt2 index 592365ba7f..e5b1e1bce8 100644 --- a/Open-ILS/src/templates/staff/circ/share/circ_strings.tt2 +++ b/Open-ILS/src/templates/staff/circ/share/circ_strings.tt2 @@ -27,6 +27,15 @@ s.LOCATION_ALERT_MSG = "{{copy.barcode()}}","{{copy.location().name()}}") %]'; s.MARK_DAMAGED_CONFIRM = '[% l("Mark {{num_items}} items as DAMAGED?") %]'; s.MARK_MISSING_CONFIRM = '[% l("Mark {{num_items}} items as MISSING?") %]'; +s.MARK_DISCARD_CONFIRM = '[% l("Mark {{num_items}} items as DICARD/WEED?") %]'; +s.MARK_ITEM_CHECKED_OUT = '[% l("Item {{barcode}} is checked out.") %]'; +s.MARK_ITEM_IN_TRANSIT = '[% l("Item {{barcode}} is in transit.") %]'; +s.MARK_ITEM_RESTRICT_DELETE = '[% l("Item {{barcode}} is in a status with a copy delete warning.") %]'; +s.MARK_ITEM_LAST_HOLD_COPY = '[% l("Item {{barcode}} is the last item to fill a hold.") %]'; +s.MARK_ITEM_CONTINUE = '[% l("Do you wish to continue marking it {{status}}?") %]'; +s.MARK_ITEM_CHECKIN_CONTINUE = '[% l("Do you wish to check it in and continue marking it {{status}}?") %]'; +s.MARK_ITEM_ABORT_CONTINUE = '[% l("Do you wish to abort the transit and continue marking it {{status}}?") %]'; +s.MARK_ITEM_FAILURE = '[% l("Marking of item {{barcode}} with status {{status}} failed: {{textcode}}") %]' s.ABORT_TRANSIT_CONFIRM = '[% l("Cancel {{num_transits}} transits?") %]'; s.ROUTE_TO_HOLDS_SHELF = '[% l("Holds Shelf") %]'; s.ROUTE_TO_CATALOGING = '[% l("Cataloging") %]'; diff --git a/Open-ILS/web/js/ui/default/staff/cat/catalog/app.js b/Open-ILS/web/js/ui/default/staff/cat/catalog/app.js index 2988d9504c..8bd06adc82 100644 --- a/Open-ILS/web/js/ui/default/staff/cat/catalog/app.js +++ b/Open-ILS/web/js/ui/default/staff/cat/catalog/app.js @@ -1671,8 +1671,22 @@ function($scope , $routeParams , $location , $window , $q , egCore , egHolds , e }); } + $scope.selectedHoldingsDiscard = function () { + var copy_list = gatherSelectedRawCopies(); + if (copy_list.length == 0) return; + egCirc.mark_discard(copy_list.map(function(cp) { + return {id: cp.id(), barcode: cp.barcode()};}).then(function() { + holdinsSvcInst.fetchAgain().then(function() { + $scop.holdingsGridDataProvider.refresh(); + }); + }); + } + $scope.selectedHoldingsMissing = function () { - egCirc.mark_missing(gatherSelectedHoldingsIds()).then(function() { + var copy_list = gatherSelectedRawCopies(); + if (copy_list.length == 0) return; + egCirc.mark_missing(copy_list.map(function(cp) { + return {id: cp.id(), barcode: cp.barcode()};}).then(function() { holdingsSvcInst.fetchAgain().then(function() { $scope.holdingsGridDataProvider.refresh(); }); diff --git a/Open-ILS/web/js/ui/default/staff/cat/item/app.js b/Open-ILS/web/js/ui/default/staff/cat/item/app.js index af59baf45b..4426ad007c 100644 --- a/Open-ILS/web/js/ui/default/staff/cat/item/app.js +++ b/Open-ILS/web/js/ui/default/staff/cat/item/app.js @@ -189,10 +189,17 @@ function($scope , $q , $window , $location , $timeout , egCore , egNet , egGridD }]); } + $scope.selectedHoldingsDiscard = function () { + itemSvc.selectedHoldingsDiscard([{ + id : $scope.args.copyId, + barcode : $scope.args.barcode + }]); + } + $scope.selectedHoldingsMissing = function () { itemSvc.selectedHoldingsMissing([{ id : $scope.args.copyId, - barcode : $scope.args.copyBarcode + barcode : $scope.args.barcode }]); } @@ -527,6 +534,10 @@ function($scope , $q , $window , $location , $timeout , egCore , egNet , egGridD itemSvc.selectedHoldingsDamaged(copyGrid.selectedItems()); } + $scope.selectedHoldingsDiscard = function () { + itemSvc.selectedHoldingsDiscard(copyGrid.selectedItems()); + } + $scope.selectedHoldingsMissing = function () { itemSvc.selectedHoldingsMissing(copyGrid.selectedItems()); } diff --git a/Open-ILS/web/js/ui/default/staff/circ/checkin/app.js b/Open-ILS/web/js/ui/default/staff/circ/checkin/app.js index 965529b4c5..d0e5106248 100644 --- a/Open-ILS/web/js/ui/default/staff/circ/checkin/app.js +++ b/Open-ILS/web/js/ui/default/staff/circ/checkin/app.js @@ -337,6 +337,20 @@ function($scope , $q , $window , $location , $timeout , egCore , checkinSvc , eg } + $scope.showMarkDiscard = function(items) { + var copies = []; + angular.forEach(items, function(item) { + if (item.acp) { + copies.push(egCore.idl.toHash(item.acp)); + } + }); + if (copies.length) { + egCirc.mark_discard(copies).then(function() { + // update grid items? + }); + } + } + $scope.abortTransit = function(items) { var transit_ids = []; angular.forEach(items, function(item) { diff --git a/Open-ILS/web/js/ui/default/staff/circ/renew/app.js b/Open-ILS/web/js/ui/default/staff/circ/renew/app.js index 2c6ba639ea..e4b7858faf 100644 --- a/Open-ILS/web/js/ui/default/staff/circ/renew/app.js +++ b/Open-ILS/web/js/ui/default/staff/circ/renew/app.js @@ -182,6 +182,19 @@ function($scope , $window , $location , egCore , egGridDataProvider , egCirc) { } } + $scope.showMarkDiscard = function(items) { + var copyies = []; + angular.forEach(items, function(item) { + if (item.acp) copies.push(egCore.idl.toHash(item.acp)); + }); + + if (copies.length) { + egCirc.mark_discard(copies).then(function() { + // update grid items? + }); + } + } + $scope.showLastFewCircs = function(items) { if (items.length && (copy = items[0].acp)) { var url = $location.path( 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 6c2d30d50e..93a5d03faf 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 @@ -1361,16 +1361,26 @@ function($uibModal , $q , egCore , egAlertDialog , egConfirmDialog, egAddCopyAl handle_checkin: !$scope.applyFine }).then(function(resp) { if (evt = egCore.evt.parse(resp)) { - doRefresh = false; - console.debug("mark damaged more information required. Pushing back."); - service.mark_damaged({ - id: params.id, - barcode: params.barcode, - charge: evt.payload.charge, - circ: evt.payload.circ, - refresh: params.refresh - }); - console.error('mark damaged failed: ' + evt); + egCore.pcrud.retrieve('ccs', 14) + .then(function(resp) { + service.handle_mark_item_event( + {id : params.id, barcode : params.barcode}, + resp, + { + apply_fines: $scope.applyFine, + override_amount: $scope.billArgs.charge, + override_btype: $scope.billArgs.type, + override_note: $scope.billArgs.note, + handle_checkin: !$scope.applyFine + }, + evt); + }).then(function(resp) { + // noop? + //if (doRefresh) egItem.add_barcode_to_list(params.barcode); + }, function(resp) { + doRefresh = false; + console.error('mark damaged failed: ' + evt); + }); } }).then(function() { if (doRefresh) egItem.add_barcode_to_list(params.barcode); @@ -1381,30 +1391,146 @@ function($uibModal , $q , egCore , egAlertDialog , egConfirmDialog, egAddCopyAl }).result; } - service.mark_missing = function(copy_ids) { + service.handle_mark_item_event = function(copy, status, args, event) { + var dlogTitle, dlogMessage; + switch (event.textcode) { + case 'ITEM_TO_MARK_CHECKED_OUT': + dlogTitle = egCore.strings.MARK_ITEM_CHECKED_OUT; + dlogMessage = egCore.strings.MARK_ITEM_CHECKIN_CONTINUE; + args.handle_checkin = 1; + break; + case 'ITEM_TO_MARK_IN_TRANSIT': + dlogTitle = egCore.strings.MARK_ITEM_IN_TRANSIT; + dlogMessage = egCore.strings.MARK_ITEM_ABORT_CONTINUE; + args.handle_transit = 1; + break; + case 'ITEM_TO_MARK_LAST_HOLD_COPY': + dlogTitle = egCore.strings.MARK_ITEM_LAST_HOLD_COPY; + dlogMessage = egCore.strings.MARK_ITEM_CONTINUE; + args.handle_last_hold_copy = 1; + break; + case 'COPY_DELETE_WARNING': + dlogTitle = egCore.strings.MARK_ITEM_RESTRICT_DELETE; + dlogMessage = egCore.strings.MARK_ITEM_CONTINUE; + args.handle_copy_delete_warning = 1; + break; + case 'PERM_FAILURE': + console.error('Mark item ' + status.name() + ' for ' + copy.barcode + ' failed: ' + + event); + return service.exit_alert(egCore.strings.PERMISSION_DENIED, + {permission : event.ilsperm}); + break; + default: + console.error('Mark item ' + status.name() + ' for ' + copy.barcode + ' failed: ' + + event); + return service.exit_alert(egCore.strings.MARK_ITEM_FAILURE, + {status : status.name(), barcode : copy.barcode, + textcode : event.textcode}); + break; + } return egConfirmDialog.open( - egCore.strings.MARK_MISSING_CONFIRM, '', - { num_items : copy_ids.length, + dlogTitle, dlogMessage, + { + barcode : copy.barcode, + status : status.name(), + ok : function () {}, + cancel : function () {} + } + ).result.then(function() { + return service.mark_item(copy, status, args); + }); + } + + service.mark_item = function(copy, markstatus, args) { + if (!copy) return $q.when(); + + // If any new back end mark_item calls are added, also add + // them here to use them from the staff client. + // TODO: I didn't find any JS constants for copy status. + var req; + switch (markstatus.id()) { + case 2: + // Not implemented in the staff client, yet. + // req = "open-ils.circ.mark_item_bindery"; + break; + case 4: + req = "open-ils.circ.mark_item_missing"; + break; + case 9: + // Not implemented in the staff client, yet. + // req = "open-ils.circ.mark_item_on_order"; + break; + case 10: + // Not implemented in the staff client, yet. + // req = "open-ils.circ.mark_item_ill"; + break; + case 11: + // Not implemented in the staff client, yet. + // req = "open-ils.circ.mark_item_cataloging"; + break; + case 12: + // Not implemented in the staff client, yet. + // req = "open-ils.circ.mark_item_reserves"; + break; + case 13: + req = "open-ils.circ.mark_item_discard"; + break; + case 14: + // Damaged is for handling of events. It's main handler is elsewhere. + req = "open-ils.circ.mark_item_damaged"; + break; + } + + return egCore.net.request( + 'open-ils.circ', + req, + egCore.auth.token(), + copy.id, + args + ).then(function(resp) { + if (evt = egCore.evt.parse(resp)) { + return service.handle_mark_item_event(copy, markstatus, args, evt); + } + }); + } + + service.mark_discard = function(copies) { + return egConfirmDialog.open( + egCore.strings.MARK_DISCARD_CONFIRM, '', + { + num_items : copies.length, ok : function() {}, cancel : function() {} } ).result.then(function() { - var promises = []; - angular.forEach(copy_ids, function(copy_id) { - promises.push( - egCore.net.request( - 'open-ils.circ', - 'open-ils.circ.mark_item_missing', - egCore.auth.token(), copy_id - ).then(function(resp) { - if (evt = egCore.evt.parse(resp)) { - console.error('mark missing failed: ' + evt); - } - }) - ); - }); + return egCore.pcrud.retrieve('ccs', 13) + .then(function(resp) { + var promises = []; + angular.forEach(copies, function(copy) { + promises.push(service.mark_item(copy, resp, {})) + }); + return $q.all(promises); + }); + }); + } - return $q.all(promises); + service.mark_missing = function(copies) { + return egConfirmDialog.open( + egCore.strings.MARK_MISSING_CONFIRM, '', + { + num_items : copies.length, + ok : function() {}, + cancel : function() {} + } + ).result.then(function() { + return egCore.pcrud.retrieve('ccs', 4) + .then(function(resp) { + var promises = []; + angular.forEach(copies, function(copy) { + promises.push(service.mark_item(copy, resp, {})) + }); + return $q.all(promises); + }); }); } diff --git a/Open-ILS/web/js/ui/default/staff/circ/services/holds.js b/Open-ILS/web/js/ui/default/staff/circ/services/holds.js index 393aeb308d..dcc1d11e6c 100644 --- a/Open-ILS/web/js/ui/default/staff/circ/services/holds.js +++ b/Open-ILS/web/js/ui/default/staff/circ/services/holds.js @@ -744,20 +744,40 @@ function($window , $location , $timeout , egCore , egHolds , egCirc) { }); } + service.mark_discard = function(items) { + var copies = items + .filter(function(item) { return Boolean(item.copy) }) + .map(function(item) { + return {id: item.copy.id(), barcode: item.copy.barcode()} + }); + if (copies.length) + egCirc.mark_discard(copies).then(service.refresh); + } + service.mark_missing = function(items) { - var copy_ids = items + var copies = items .filter(function(item) { return Boolean(item.copy) }) - .map(function(item) { return item.copy.id() }); - if (copy_ids.length) - egCirc.mark_missing(copy_ids).then(service.refresh); + .map(function(item) { + return {id: item.copy.id(), barcode: item.copy.barcode()} + }); + if (copies.length) + egCirc.mark_missing(copies).then(service.refresh); } service.mark_missing_wide = function(items) { - var copy_ids = items + var copies = items + .filter(function(item) { return Boolean(item.hold.cp_id) }) + .map(function(item) { return {id: item.hold.cp_id, barcode: item.hold.cp_barcode}; }); + if (copies.length) + egCirc.mark_missing(copies).then(service.refresh); + } + + service.mark_discard_wide = function(items) { + var copies = items .filter(function(item) { return Boolean(item.hold.cp_id) }) - .map(function(item) { return item.hold.cp_id }); - if (copy_ids.length) - egCirc.mark_missing(copy_ids).then(service.refresh); + .map(function(item) { return {id: item.hold.cp_id, barcode: item.hold.cp_barcode}; }); + if (copies.length) + egCirc.mark_discard(copies).then(service.refresh); } service.retarget = function(items) { diff --git a/Open-ILS/web/js/ui/default/staff/circ/services/item.js b/Open-ILS/web/js/ui/default/staff/circ/services/item.js index c39f0380be..6382852d32 100644 --- a/Open-ILS/web/js/ui/default/staff/circ/services/item.js +++ b/Open-ILS/web/js/ui/default/staff/circ/services/item.js @@ -644,8 +644,14 @@ function(egCore , egCirc , $uibModal , $q , $timeout , $window , egConfirmDialog }); } + service.selectedHoldingsDiscard = function (items) { + egCirc.mark_discard(items.map(function(el){return {id : el.id, barcode : el.barcode};})).then(function(){ + angular.forEach(items, function(cp){service.add_barcode_to_list(cp.barcode)}); + }); + } + service.selectedHoldingsMissing = function (items) { - egCirc.mark_missing(items.map(function(el){return el.id;})).then(function(){ + egCirc.mark_missing(items.map(function(el){return {id : el.id, barcode : el.barcode};})).then(function(){ angular.forEach(items, function(cp){service.add_barcode_to_list(cp.barcode)}); }); } diff --git a/docs/RELEASE_NOTES_NEXT/Circulation/enhanced-mark-item-functionality.adoc b/docs/RELEASE_NOTES_NEXT/Circulation/enhanced-mark-item-functionality.adoc new file mode 100644 index 0000000000..9e02269012 --- /dev/null +++ b/docs/RELEASE_NOTES_NEXT/Circulation/enhanced-mark-item-functionality.adoc @@ -0,0 +1,112 @@ +Enhanced Mark Item Functionality +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Evergreen's Mark Item Damaged and Mark Item Missing functionality has +been enhanced, and the ability to mark an item with the Discard/Weed +status has been added. This enhancement affects both the Evergreen +back end code and the staff client. + +Staff Client Changes +++++++++++++++++++++ + +The option to "Mark Item as Discard/Weed" has been added to areas +where the option(s) to "Mark Item as Missing" and/or "Mark Item as +Damaged" appear. This is primarily in the action menus on the +following interfaces: + + * Item Status + * Checkin + * Renew + * Holds Pull List + * Patron Holds List + * Record Holds List + * Holds Shelf + * Holdings Edit + +This new option allows staff to mark a copy with the Discard/Weed +status quickly and easily without necessarily requiring the +intervention of cataloging staff. In order to mark an item with the +Discard/Weed status, staff will require either the `MARK_ITEM_DISCARD` +or `UPDATE_COPY status` at the item's owning library. (NOTE: This +permission choice is consistent with the permission requirements for +the current Mark Item Damaged or Missing functionality.) + +If the item to be marked Discard/Weed is checked out to a patron, the +staff will be presented with a dialog informing them that the item is +checked out and asking if they would like to check it in and proceed. +If they choose to continue, the item will be checked in and then +marked with the Discard/Weed status. If the staff person chooses to +cancel, then the item will not be checked in, and it will not be +marked Discard/Weed. The Mark Item Missing functionality has also +been changed to exhibit this behavior with checked out items. The +Mark Item Damaged functionality already handles checked out item. + +Should the item have a status of In Transit at the time it is to be +marked, then staff will be prompted to abort the transit before +proceeding with changing the item's status. If they choose to abort +the transit and they have the permission to do so, the transit will be +aborted and the item's status changed. If they choose to cancel, then +the transit will not be aborted and the item's status will remain +unchanged. This change applies to all three of the current Mark Item +statuses: Missing, Damaged, and Discard/Weed. + +Marking an item Discard/Weed is typically one step away from deleting +the item. For this reason, if the item to be marked Discard/Weed is +not in a Checked Out or In Transit status, but it is in a status that +restricts item deletion, the staff will be presented with a dialog +notifying them of the item's status and asking if they wish to +proceed. If staff choose to proceed and they have the +`COPY_DELETE_WARNING.override` permission, then the item will be +marked with the Discard/Weed status. Naturally, the item's status +will be unchanged if they choose not to proceed. This change does not +affect the marking of an item as Missing or Damaged. + +Marking an item as Discard/Weed has one more additional check that the +other statuses do not. If the item being marked as Discard/Weed is +the last copy that can fill a hold, then staff will also be notified +of this condition and asked if they wish to continue. In this case, +there is no permission required. Whether or not the item is marked as +Discard/Weed in this case depends solely on the staff's choice. + +Back End Changes +++++++++++++++++ + +In order to accommodate the presentation of dialogs and overrides in +the staff client, the `OpenILS::Application::Circ` module's method for +marking item statuses has had a few changes made. Firstly, the code +of the `mark_item` function has been rearranged to a more logical +flow. Most of the condition and permission checks are made before +creating a transaction. Secondly, it has been modified to return 3 +new events when certain conditions are met: + + * `ITEM_TO_MARK_CHECKED_OUT` + * `ITEM_TO_MARK_IN_TRANSIT` + * `ITEM_TO_MARK_LAST_HOLD_COPY` + +The `COPY_DELETE_WARNING` event will be returned when attempting to +mark an item with the Discard/Weed status and the status has the +`restrict_copy_delete` flag set to true. + +The function now also recognizes a hash of extra arguments for all +statuses and not just for the mark Damaged functionality. This +argument hash can be used to bypass or override any or all of the +above mentioned events. Each event has a corresponding argument that +if set to a "true" value will cause the `mark_item` to bypass the +given event. These argument flags are, respectively: + + * `handle_checkin` + * `handle_transit` + * `handle_last_hold_copy` + * `handle_copy_delete_warning` + +The code to mark an item damaged still accepts its previous hash +arguments in addition to these new ones. + +The function still returns other errors and events as before. It +still returns 1 on success. + +It is also worth noting here that the staff client can be easily +extended with the ability to mark items into the other statuses +offered by the back end functions. Most of the staff client +functionality is implemented in two functions with placeholders in the +main function (`egCirc.mark_item`) for the unimplemented statuses.