From 3770ebc0e92c664c64042f71c8283f48a782e8c2 Mon Sep 17 00:00:00 2001 From: Martin Renvoize Date: Thu, 23 Sep 2021 13:58:50 +0100 Subject: [PATCH] Bug 28854: Record and display who lost the item This patch records the bundle issue from which an item is marked as lost so that we may use that to infer who lost the item (for later charges and display). Test plan 0) Apply all patches up to this point 1) Checkout a bundle to a user 2) Checkin the bundle and do not scan one of the barcodes at confirmation * Note that the item not scanned is marked as lost 3) Navigate to the biblio for the lost item and note that it is marked as lost. 4) Navigate to the biblio for the collection and expand the collection item that contains the lost item. Note the item is marked as lost and checkout details are listed. 5) Checkin the lost item * The item should be marked as found and the return_claims line should be marked as resolved. Signed-off-by: Katrin Fischer Signed-off-by: Kyle M Hall Signed-off-by: Tomas Cohen Arazi --- C4/Accounts.pm | 7 +- Koha/Item.pm | 16 ++- circ/returns.pl | 109 ++++++++++++------ .../prog/en/includes/html_helpers.inc | 2 + .../prog/en/modules/catalogue/detail.tt | 7 +- .../prog/en/modules/circ/returns.tt | 19 +++ 6 files changed, 118 insertions(+), 42 deletions(-) diff --git a/C4/Accounts.pm b/C4/Accounts.pm index ad0d34f184..84ebbcd225 100644 --- a/C4/Accounts.pm +++ b/C4/Accounts.pm @@ -70,7 +70,8 @@ FIXME : if no replacement price, borrower just doesn't get charged? sub chargelostitem { my $dbh = C4::Context->dbh(); my ($borrowernumber, $itemnumber, $amount, $description) = @_; - my $itype = Koha::ItemTypes->find({ itemtype => Koha::Items->find($itemnumber)->effective_itemtype() }); + my $item = Koha::Items->find($itemnumber); + my $itype = Koha::ItemTypes->find({ itemtype => $item->effective_itemtype() }); my $replacementprice = $amount; my $defaultreplacecost = $itype->defaultreplacecost; my $processfee = $itype->processfee; @@ -80,6 +81,10 @@ sub chargelostitem { $replacementprice = $defaultreplacecost; } my $checkout = Koha::Checkouts->find({ itemnumber => $itemnumber }); + if ( !$checkout && $item->in_bundle ) { + my $host = $item->bundle_host; + $checkout = $host->checkout; + } my $issue_id = $checkout ? $checkout->issue_id : undef; my $account = Koha::Account->new({ patron_id => $borrowernumber }); diff --git a/Koha/Item.pm b/Koha/Item.pm index bb587a1caa..bf377bfe3d 100644 --- a/Koha/Item.pm +++ b/Koha/Item.pm @@ -441,6 +441,20 @@ sub item_group { return $item_group; } +=head3 return_claims + + my $return_claims = $item->return_claims; + +Return any return_claims associated with this item + +=cut + +sub return_claims { + my ( $self, $params, $attrs ) = @_; + my $claims_rs = $self->_result->return_claims->search($params, $attrs); + return Koha::Checkouts::ReturnClaims->_new_from_dbic( $claims_rs ); +} + =head3 holds my $holds = $item->holds(); @@ -1114,7 +1128,7 @@ Internal function, not exported, called only by Koha::Item->store. sub _set_found_trigger { my ( $self, $pre_mod_item ) = @_; - ## If item was lost, it has now been found, reverse any list item charges if necessary. + # Reverse any lost item charges if necessary. my $no_refund_after_days = C4::Context->preference('NoRefundOnLostReturnedItemsAge'); if ($no_refund_after_days) { diff --git a/circ/returns.pl b/circ/returns.pl index 52df948d14..545de9b502 100755 --- a/circ/returns.pl +++ b/circ/returns.pl @@ -391,43 +391,6 @@ if ($barcode) { } } - # 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; @@ -445,6 +408,78 @@ if ($barcode) { items_bundle_return_confirmation => 1, ); } + + # Mark missing bundle items as lost and report unexpected items + if ( $item->is_bundle && $query->param('confirm_items_bundle_return') ) { + 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); + + # Update last_seen + $verify_item->datelastseen( dt_from_string()->ymd() ); + + # Update last_borrowed if actual checkin + $verify_item->datelastborrowed( dt_from_string()->ymd() ) if $issue; + + # 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(); + # Add return_claim record if this is an actual checkin + if ($issue) { + $bundle_item->_result->create_related( + 'return_claims', + { + issue_id => $issue->issue_id, + itemnumber => $bundle_item->itemnumber, + borrowernumber => $issue->borrowernumber, + created_by => C4::Context->userenv()->{number}, + created_on => dt_from_string + } + ); + } + push @missing_items, $bundle_item; + # NOTE: We cannot use C4::LostItem here because the item itself doesn't have a checkout + # and thus would not get charged.. it's checked out as part of the bundle. + if ( C4::Context->preference('WhenLostChargeReplacementFee') && $issue ) { + C4::Accounts::chargelostitem( + $issue->borrowernumber, + $bundle_item->itemnumber, + $bundle_item->replacementprice, + sprintf( "%s %s %s", + $bundle_item->biblio->title || q{}, + $bundle_item->barcode || q{}, + $bundle_item->itemcallnumber || q{}, + ), + ); + } + } + $template->param( + unexpected_items => \@unexpected_items, + missing_items => \@missing_items, + bundle_items => \@bundle_items + ); + } } $template->param( inputloop => \@inputloop ); diff --git a/koha-tmpl/intranet-tmpl/prog/en/includes/html_helpers.inc b/koha-tmpl/intranet-tmpl/prog/en/includes/html_helpers.inc index fdcc6eadfa..6ec8a50c8f 100644 --- a/koha-tmpl/intranet-tmpl/prog/en/includes/html_helpers.inc +++ b/koha-tmpl/intranet-tmpl/prog/en/includes/html_helpers.inc @@ -115,6 +115,8 @@ [% ELSE %] [% IF subfield.IS_LOST_AV && Koha.Preference("ClaimReturnedLostValue") && aval == Koha.Preference("ClaimReturnedLostValue") %] + [% ELSIF subfield.IS_LOST_AV && Koha.Preference("BundleLostValue") && aval == Koha.Preference("BundleLostValue") %] + [% ELSE %] [% END %] diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/catalogue/detail.tt b/koha-tmpl/intranet-tmpl/prog/en/modules/catalogue/detail.tt index 1cebad0cc3..07674e2ef0 100644 --- a/koha-tmpl/intranet-tmpl/prog/en/modules/catalogue/detail.tt +++ b/koha-tmpl/intranet-tmpl/prog/en/modules/catalogue/detail.tt @@ -1577,6 +1577,7 @@ Note that permanent location is a code, and location may be an authval. [% IF bundlesEnabled %] var bundle_settings = [% TablesSettings.GetTableSettings('catalogue', 'detail','bundle_tables','json') | $raw %]; + var bundle_lost_value = [% Koha.Preference('BundleLostValue') %]; [% END %] $(document).ready(function() { @@ -1668,10 +1669,10 @@ Note that permanent location is a code, and location may be an authval. "searchable": false, "orderable": true, "render": function(data, type, row, meta) { - if ( row.lost_status ) { - return "Lost: " + row.lost_status; + if ( row.lost_status == bundle_lost_value ) { + return "Last seen: " + row.last_seen_date; } - return ""; + return "Present"; } }, { 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 5692d0c432..ad7791bc35 100644 --- a/koha-tmpl/intranet-tmpl/prog/en/modules/circ/returns.tt +++ b/koha-tmpl/intranet-tmpl/prog/en/modules/circ/returns.tt @@ -429,11 +429,14 @@ [% t('Author') | html %] [% t('Item type') | html %] [% t('Barcode') | html %] + [% IF !item.onloan %] [% t('Status') | html %] + [% END %] [% FOREACH bundle_item IN item.bundle_items %] + [% IF !item.onloan %] [% INCLUDE 'biblio-title.inc' biblio=bundle_item.biblio link = 1 %] [% bundle_item.biblio.author | html %] @@ -441,6 +444,14 @@ [% bundle_item.barcode | html %] [% INCLUDE 'item-status.inc' item=bundle_item %] + [% ELSIF !bundle_item.itemlost %] + + [% INCLUDE 'biblio-title.inc' biblio=bundle_item.biblio link = 1 %] + [% bundle_item.biblio.author | html %] + [% ItemTypes.GetDescription(bundle_item.itype) | html %] + [% bundle_item.barcode | html %] + + [% END %] [% END %] @@ -448,7 +459,11 @@
+ [% IF item.onloan %]
[% t('Scan all barcodes of items found in the items bundle. If any items are missing, they will be marked as lost') | html %]
+ [% ELSE %] +
[% t('Optionally scan all barcodes of items found in the items bundle to perform an inventory check. If any items are missing, they will be marked as lost') | html %]
+ [% END %]
@@ -460,7 +475,11 @@ [% END %] + [% IF item.onloan %] + [% ELSE %] + + [% END %] -- 2.39.5