From c81f46a61b941bae222f8ab290704de74a7e8e43 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 | 58 +++++++- .../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, 320 insertions(+), 3 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 f5df973d1d..4c9779a491 100644 --- a/C4/Circulation.pm +++ b/C4/Circulation.pm @@ -2433,6 +2433,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 340efb4779..52df948d14 100755 --- a/circ/returns.pl +++ b/circ/returns.pl @@ -338,10 +338,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'); @@ -382,7 +390,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; @@ -393,6 +439,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 ); @@ -631,6 +683,8 @@ foreach my $code ( keys %$messages ) { ; } elsif ( $code eq 'TransferredRecall' ) { ; + } 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 6f483a9e30..a62600d96f 100644 --- a/koha-tmpl/intranet-tmpl/prog/css/src/_tables.scss +++ b/koha-tmpl/intranet-tmpl/prog/css/src/_tables.scss @@ -359,7 +359,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..fe0041923c --- /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 bc3520c731..bc53efc0d8 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 ab184328ea..9d95939065 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 %]