From 97dbc22b519a5b4ebc2dfa7c186d0ac693fbce99 Mon Sep 17 00:00:00 2001 From: Martin Renvoize Date: Thu, 23 Sep 2021 11:37:52 +0100 Subject: [PATCH] Bug 28854: Update circulation functionality for bundles This patch updates the circulation system to account for bundle checkins. We add a content verification step to ensure bundle content is all present at checkin and we use this comparison to mark missing items as lost. Test plan 0) Apply patches up to this point 1) Checkin an item that belongs to a bundle * An alert should be triggered noting that the item belongs to a bundle * The option to remove the item from the bundle should be clear * Click remove should result in the alert dissapearing and the item having been removed from the bundle. 2) Checkin an item bundle * A modal confirmation dialog should appear requesting each item barcode be scanned * As items are scanned they should be highlighted in yellow in the bundle content table * Upon submission; * The user will be alerted to any unexpected items that were scanned and told to put them to one side. * The user will be alerted that any missing items in the validation will have been marked as lost. * The bundle item will be marked as checked in. --- C4/Circulation.pm | 6 + circ/returns.pl | 63 +++++++- .../intranet-tmpl/prog/css/src/_tables.scss | 2 +- .../prog/en/includes/item-status.inc | 84 +++++++++++ .../en/includes/modals/bundle_contents.inc | 35 +++++ .../admin/preferences/circulation.pref | 4 + .../prog/en/modules/circ/returns.tt | 134 ++++++++++++++++++ 7 files changed, 323 insertions(+), 5 deletions(-) create mode 100644 koha-tmpl/intranet-tmpl/prog/en/includes/item-status.inc create mode 100644 koha-tmpl/intranet-tmpl/prog/en/includes/modals/bundle_contents.inc diff --git a/C4/Circulation.pm b/C4/Circulation.pm index c2edf81f94..3b141a7c4b 100644 --- a/C4/Circulation.pm +++ b/C4/Circulation.pm @@ -2311,6 +2311,12 @@ sub AddReturn { } } + # Check for bundle status + if ( $item->in_bundle ) { + my $host = $item->bundle_host; + $messages->{InBundle} = $host; + } + my $indexer = Koha::SearchEngine::Indexer->new({ index => $Koha::SearchEngine::BIBLIOS_INDEX }); $indexer->index_records( $item->biblionumber, "specialUpdate", "biblioserver" ); diff --git a/circ/returns.pl b/circ/returns.pl index 8ab9986d91..e48293512a 100755 --- a/circ/returns.pl +++ b/circ/returns.pl @@ -38,7 +38,6 @@ use C4::Auth qw( get_template_and_user get_session haspermission ); use C4::Output qw( output_html_with_http_headers ); use C4::Circulation qw( barcodedecode GetBranchItemRule AddReturn updateWrongTransfer LostItem ); use C4::Reserves qw( ModReserve ModReserveAffect GetOtherReserves ); -use C4::Circulation qw( barcodedecode GetBranchItemRule AddReturn updateWrongTransfer LostItem ); use C4::Context; use C4::Items qw( ModItemTransfer ); use C4::Members::Messaging; @@ -301,10 +300,18 @@ if ($barcode) { $template->param( 'multiple_confirmed' => 1 ) if $query->param('multiple_confirm'); + # Block return if bundle and confirm has not been received + my $bundle_confirm = + $item + && $item->is_bundle + && !$query->param('confirm_items_bundle_return'); + $template->param( 'confirm_items_bundle_returned' => 1 ) + if $query->param('confirm_items_bundle_return'); + # do the return ( $returned, $messages, $issue, $borrower ) = AddReturn( $barcode, $userenv_branch, $exemptfine, $return_date ) - unless $needs_confirm; + unless ( $needs_confirm || $bundle_confirm ); if ($returned) { my $time_now = dt_from_string()->truncate( to => 'minute'); @@ -345,7 +352,45 @@ if ($barcode) { ); } } - } elsif ( C4::Context->preference('ShowAllCheckins') and !$messages->{'BadBarcode'} and !$needs_confirm ) { + + # Mark missing bundle items as lost and report unexpected items + if ( $item->is_bundle ) { + my $BundleLostValue = C4::Context->preference('BundleLostValue'); + my $barcodes = $query->param('verify-items-bundle-contents-barcodes'); + my @barcodes = map { s/^\s+|\s+$//gr } ( split /\n/, $barcodes ); + my $expected_items = { map { $_->barcode => $_ } $item->bundle_items->as_list }; + my $verify_items = Koha::Items->search( { barcode => { 'in' => \@barcodes } } ); + my @unexpected_items; + my @missing_items; + my @bundle_items; + while ( my $verify_item = $verify_items->next ) { + # Fix and lost statuses + $verify_item->itemlost(0); + + # Expected item, remove from lookup table + if ( delete $expected_items->{$verify_item->barcode} ) { + push @bundle_items, $verify_item; + } + # Unexpected item, warn and remove from bundle + else { + $verify_item->remove_from_bundle; + push @unexpected_items, $verify_item; + } + # Store results + $verify_item->store(); + } + for my $missing_item ( keys %{$expected_items} ) { + my $bundle_item = $expected_items->{$missing_item}; + $bundle_item->itemlost($BundleLostValue)->store(); + push @missing_items, $bundle_item; + } + $template->param( + unexpected_items => \@unexpected_items, + missing_items => \@missing_items, + bundle_items => \@bundle_items + ); + } + } elsif ( C4::Context->preference('ShowAllCheckins') and !$messages->{'BadBarcode'} and !$needs_confirm and !$bundle_confirm ) { $input{duedate} = 0; $returneditems{0} = $barcode; $riduedate{0} = 0; @@ -356,6 +401,12 @@ if ($barcode) { if ( $needs_confirm ) { $template->param( needs_confirm => $needs_confirm ); } + + if ( $bundle_confirm ) { + $template->param( + items_bundle_return_confirmation => 1, + ); + } } $template->param( inputloop => \@inputloop ); @@ -553,7 +604,11 @@ foreach my $code ( keys %$messages ) { } elsif ( $code eq 'ReturnClaims' ) { $template->param( ReturnClaims => $messages->{ReturnClaims} ); - } else { + } + elsif ( $code eq 'InBundle' ) { + $template->param( InBundle => $messages->{InBundle} ); + } + else { die "Unknown error code $code"; # note we need all the (empty) elsif's above, or we die. # This forces the issue of staying in sync w/ Circulation.pm } diff --git a/koha-tmpl/intranet-tmpl/prog/css/src/_tables.scss b/koha-tmpl/intranet-tmpl/prog/css/src/_tables.scss index 42c3452c5e..ca8bae6889 100644 --- a/koha-tmpl/intranet-tmpl/prog/css/src/_tables.scss +++ b/koha-tmpl/intranet-tmpl/prog/css/src/_tables.scss @@ -363,7 +363,7 @@ tbody { border-right: 1px solid $table-border-color; } } - &:nth-child(odd):not(.dtrg-group):not(.active) { + &:nth-child(odd):not(.dtrg-group):not(.active):not(.ok) { td { &:not(.bg-danger):not(.bg-warning):not(.bg-info):not(.bg-success):not(.bg-primary) { background-color: $table-odd-row; diff --git a/koha-tmpl/intranet-tmpl/prog/en/includes/item-status.inc b/koha-tmpl/intranet-tmpl/prog/en/includes/item-status.inc new file mode 100644 index 0000000000..ca6dc1d1ff --- /dev/null +++ b/koha-tmpl/intranet-tmpl/prog/en/includes/item-status.inc @@ -0,0 +1,84 @@ +[% USE AuthorisedValues %] +[% USE Branches %] +[% USE Koha %] +[% USE KohaDates %] +[% PROCESS 'i18n.inc' %] +[% transfer = item.get_transfer() %] +[% IF item.checkout %] + + [% IF item.checkout.onsite_checkout %] + [% t('Currently in local use by') | html %] + [% ELSE %] + [% t('Checked out to') | html %] + [% END %] + [% INCLUDE 'patron-title.inc' patron=item.checkout.patron hide_patron_infos_if_needed=1 %] + : [% tx('due {date_due}', { date_due = item.checkout.date_due }) | html %] + +[% ELSIF transfer %] + [% datesent = BLOCK %][% transfer.datesent | $KohaDates %][% END %] + [% tx('In transit from {frombranch} to {tobranch} since {datesent}', { frombranch = Branches.GetName(transfer.frombranch), tobranch = Branches.GetName(transfer.tobranch), datesent = datesent }) %] +[% END %] + +[% IF item.itemlost %] + [% itemlost_description = AuthorisedValues.GetDescriptionByKohaField({ kohafield = 'items.itemlost', authorised_value = item.itemlost }) %] + [% IF itemlost_description %] + [% itemlost_description | html %] + [% ELSE %] + [% t('Unavailable (lost or missing)') | html %] + [% END %] +[% END %] + +[% IF item.withdrawn %] + [% withdrawn_description = AuthorisedValues.GetDescriptionByKohaField({ kohafield = 'items.withdrawn', authorised_value = item.withdrawn }) %] + [% IF withdrawn_description %] + [% withdrawn_description | html %] + [% ELSE %] + [% t('Withdrawn') | html %] + [% END %] +[% END %] + +[% IF item.damaged %] + [% damaged_description = AuthorisedValues.GetDescriptionByKohaField({ kohafield = 'items.damaged', authorised_value = item.damaged }) %] + [% IF damaged_description %] + [% damaged_description | html %] + [% ELSE %] + [% t('Damaged') | html %] + [% END %] +[% END %] + +[% IF item.notforloan || item.effective_itemtype.notforloan %] + + [% t('Not for loan') | html %] + [% notforloan_description = AuthorisedValues.GetDescriptionByKohaField({ kohafield = 'items.notforloan', authorised_value = item.notforloan }) %] + [% IF notforloan_description %] + ([% notforloan_description | html %]) + [% END %] + +[% END %] + +[% hold = item.holds.next %] +[% IF hold %] + + [% IF hold.waitingdate %] + Waiting at [% Branches.GetName(hold.get_column('branchcode')) | html %] since [% hold.waitingdate | $KohaDates %]. + [% ELSE %] + Item-level hold (placed [% hold.reservedate | $KohaDates %]) for delivery at [% Branches.GetName(hold.get_column('branchcode')) | html %]. + [% END %] + [% IF Koha.Preference('canreservefromotherbranches') %] + [% t('Hold for:') | html %] + [% INCLUDE 'patron-title.inc' patron=hold.borrower hide_patron_infos_if_needed=1 %] + [% END %] + +[% END %] +[% UNLESS item.notforloan || item.effective_itemtype.notforloan || item.onloan || item.itemlost || item.withdrawn || item.damaged || transfer || hold %] + [% t('Available') | html %] +[% END %] + +[% IF ( item.restricted ) %] + [% restricted_description = AuthorisedValues.GetDescriptionByKohaField({ kohafield = 'items.restricted', authorised_value = item.restricted }) %] + [% IF restricted_description %] + ([% restricted_description | html %]) + [% ELSE %] + [% t('Restricted') | html %] + [% END %] +[% END %] diff --git a/koha-tmpl/intranet-tmpl/prog/en/includes/modals/bundle_contents.inc b/koha-tmpl/intranet-tmpl/prog/en/includes/modals/bundle_contents.inc new file mode 100644 index 0000000000..c88822b4e9 --- /dev/null +++ b/koha-tmpl/intranet-tmpl/prog/en/includes/modals/bundle_contents.inc @@ -0,0 +1,35 @@ + + diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/admin/preferences/circulation.pref b/koha-tmpl/intranet-tmpl/prog/en/modules/admin/preferences/circulation.pref index 6c2a33bc6c..90317e316d 100644 --- a/koha-tmpl/intranet-tmpl/prog/en/modules/admin/preferences/circulation.pref +++ b/koha-tmpl/intranet-tmpl/prog/en/modules/admin/preferences/circulation.pref @@ -12,6 +12,10 @@ Circulation: 1: "Require" 0: "Don't require" - staff to confirm that all parts of an item are present at checkin/checkout. + - + - Use the LOST authorised value + - pref: BundleLostValue + - to represent 'missing from bundle' at return. - - pref: AutoSwitchPatron choices: diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/circ/returns.tt b/koha-tmpl/intranet-tmpl/prog/en/modules/circ/returns.tt index 731ec996e5..75918df8fd 100644 --- a/koha-tmpl/intranet-tmpl/prog/en/modules/circ/returns.tt +++ b/koha-tmpl/intranet-tmpl/prog/en/modules/circ/returns.tt @@ -6,6 +6,7 @@ [% USE ItemTypes %] [% USE AuthorisedValues %] [% USE TablesSettings %] +[% PROCESS 'i18n.inc' %] [% PROCESS 'member-display-address-style.inc' %] [% SET footerjs = 1 %] [% BLOCK display_bormessagepref %] @@ -217,6 +218,36 @@ [% END %] + + [% IF missing_items %] +
+

Bundle had missing items

+

Bundle contents list updated

+

View updated contents list

+
+ [% END %] + + + [% IF unexpected_items %] +
+

Bundle had unexpected items

+

Please place the following items to one side

+ +
+ [% END %] + + + [% IF InBundle %] +
+

Item belongs in bundle

+

This item belongs to a bundle: [% INCLUDE 'biblio-title.inc' biblio=InBundle.biblio %] - [% InBundle.barcode | html %]

+

+
+ [% END %] [% IF ( errmsgloop ) %]
@@ -381,6 +412,63 @@
[% END %] + [% IF items_bundle_return_confirmation %] + + [% END %] + [% IF wrongbranch %]