From 8ce2b28294b91ce2dc5937ac0e3672740852aeb9 Mon Sep 17 00:00:00 2001 From: Jonathan Druart Date: Wed, 2 Oct 2024 14:52:18 +0200 Subject: [PATCH] Bug 37334: Restore filtering holdings per status Signed-off-by: Lucas Gass Signed-off-by: Emmi Takkinen --- Koha/Item.pm | 6 +- Koha/Items.pm | 130 ++++++++++++++++++ .../tables/items/catalogue_detail.inc | 22 ++- 3 files changed, 146 insertions(+), 12 deletions(-) diff --git a/Koha/Item.pm b/Koha/Item.pm index f6bc0f6951b..be6802a09d3 100644 --- a/Koha/Item.pm +++ b/Koha/Item.pm @@ -1454,11 +1454,7 @@ sub _status { push @statuses, "local_use"; } } elsif ( my $transfer = $self->transfer ) { - if ( $transfer->datesent ) { - push @statuses, "in_transit"; - } else { - push @statuses, "transit_pending"; - } + push @statuses, "in_transit"; } if ( $self->itemlost ) { push @statuses, 'lost'; diff --git a/Koha/Items.pm b/Koha/Items.pm index 1cb1dce1b5c..e107d217f80 100644 --- a/Koha/Items.pm +++ b/Koha/Items.pm @@ -222,6 +222,91 @@ sub filter_by_bookable { ); } +sub filter_by_checked_out { + my ( $self, $params ) = @_; + + $params //= {}; + my $checkouts = Koha::Checkouts->search( + { %$params, 'me.itemnumber' => [ $self->get_column('itemnumber') ], }, + { + columns => ['itemnumber'], + distinct => 1 + } + )->_resultset->as_query; + + return $self->search( { 'me.itemnumber' => { '-in' => $checkouts } } ); +} + +sub filter_by_in_transit { + my ( $self, $params ) = @_; + + $params //= {}; + my $transfers = Koha::Item::Transfers->search( + { %$params, 'me.itemnumber' => [ $self->get_column('itemnumber') ], }, + { + columns => ['itemnumber'], + distinct => 1 + } + )->_resultset->as_query; + + return $self->search( { 'me.itemnumber' => { '-in' => $transfers } } ); +} + +sub filter_by_for_loan { + +} + +sub filter_by_has_holds { + my ( $self, $params ) = @_; + + $params //= {}; + my $holds = Koha::Holds->search( + { %$params, 'me.itemnumber' => [ $self->get_column('itemnumber') ], }, + { + columns => ['itemnumber'], + distinct => 1 + } + )->_resultset->as_query; + + return $self->search( { 'me.itemnumber' => { '-in' => $holds } } ); +} + +sub filter_by_has_recalls { + my ( $self, $params ) = @_; + + $params //= {}; + my $recalls = Koha::Recalls->search( + { %$params, 'me.itemnumber' => [ $self->get_column('itemnumber') ], }, + { + columns => ['itemnumber'], + distinct => 1 + } + )->_resultset->as_query; + + return $self->search( { 'me.itemnumber' => { '-in' => $recalls } } ); +} + +sub filter_by_available { + my ($self) = @_; + + my @all_itemnumbers = $self->get_column('itemnumber'); + my @item_types_notforloan = Koha::ItemTypes->search( { notforloan => { '!=' => 0 } } )->get_column('itemtype'); + my @not_available_itemnumbers; + push @not_available_itemnumbers, $self->filter_by_checked_out->get_column('itemnumber'); + push @not_available_itemnumbers, $self->filter_by_in_transit->get_column('itemnumber'); + push @not_available_itemnumbers, $self->search( + { + itemlost => 0, withdrawn => 0, damaged => 0, notforloan => 0, + itype => { -not_in => \@item_types_notforloan }, restricted => 0, + } + )->get_column('itemnumber'); + push @not_available_itemnumbers, $self->filter_by_has_holds->get_column('itemnumber'); + push @not_available_itemnumbers, $self->filter_by_has_recalls->get_column('itemnumber'); + + return Koha::Items->search( { 'me.itemnumber' => [ array_minus @all_itemnumbers, @not_available_itemnumbers ] } ); +} + + =head3 move_to_biblio $items->move_to_biblio($to_biblio); @@ -485,6 +570,51 @@ sub apply_regex { return $value; } +sub search { + my ( $self, $params, $attributes ) = @_; + my $status = ( $params && ref($params) eq 'HASH' ) ? delete $params->{_status} : undef; + if ($status) { + if ( $status eq 'checked_out' ) { + $self = $self->filter_by_checked_out( { onsite_checkout => 0 } ); + } + if ( $status eq 'local_use' ) { + $self = $self->filter_by_checked_out( { onsite_checkout => 1 } ); + } + if ( $status eq 'in_transit' ) { + $self = $self->filter_by_in_transit; + } + if ( $status eq 'lost' ) { + $self = $self->search( { itemlost => { '!=' => 0 } } ); + } + if ( $status eq 'withdrawn' ) { + $self = $self->search( { withdrawn => { '!=' => 0 } } ); + } + if ( $status eq 'damaged' ) { + $self = $self->search( { damaged => { '!=' => 0 } } ); + } + if ( $status eq 'not_for_loan' ) { + my @item_types_notforloan = Koha::ItemTypes->search( { notforloan => { '!=' => 0 } } )->get_column('itemtype'); + $self = $self->search( [ { notforloan => { '!=' => 0 } }, { 'me.itype' => \@item_types_notforloan } ] ); + } + if ( $status eq 'on_hold' ) { + $self = $self->filter_by_has_holds; + } + if ( $status eq 'recalled' ) { + $self = $self->filter_by_has_recalls; + } + + if ( $status eq 'available' ) { + $self = $self->filter_by_available; + } + + if ( $status eq 'restricted' ) { + $self = $self->search( { restricted => { '!=' => 0 } } ); + } + } + + return $self->SUPER::search( $params, $attributes ); +} + =head3 search_ordered $items->search_ordered; diff --git a/koha-tmpl/intranet-tmpl/prog/en/includes/html_helpers/tables/items/catalogue_detail.inc b/koha-tmpl/intranet-tmpl/prog/en/includes/html_helpers/tables/items/catalogue_detail.inc index 5c9624cea7d..ef021275ef1 100644 --- a/koha-tmpl/intranet-tmpl/prog/en/includes/html_helpers/tables/items/catalogue_detail.inc +++ b/koha-tmpl/intranet-tmpl/prog/en/includes/html_helpers/tables/items/catalogue_detail.inc @@ -216,6 +216,9 @@ }); const item_types_notforloan = new Map(all_item_types.map( it => [it.itemtype, it.notforloan] )); + const statuses = {checked_out: _("Checked out"), local_use: _("On-site checkout"), in_transit: _("In transit"), lost: _("Lost"), withdrawn: _("Withdrawn"), damaged:_("Damaged"), not_for_loan: _("Not for loan"), on_hold: _("On hold"), recalled: _("Recalled"), available: _("Available"), restricted: _("Restricted")}; + const all_statuses = Object.keys(statuses).map(k => {return {_id: k, _str: statuses[k]}}); + const can_edit_items_from = [% To.json(can_edit_items_from || []) | $raw %]; const item_type_image_locations = [% To.json(item_type_image_locations) | $raw %]; const av_loc = new Map([% To.json(AuthorisedValues.Get('LOC')) | $raw %].map( av => [av.authorised_value, av.lib])); @@ -307,12 +310,17 @@ items_selection[tab_id] = []; } + default_filters._status = function(){ + return $("#" + tab_id + "_status select").val(); + }; + let offset = 2; [% UNLESS Koha.Preference('LocalCoverImages') %]offset--;[% END %] let filters_options = { [offset] : () => all_item_types, [offset+1] : () => all_libraries, [offset+2] : () => all_libraries, + [offset+6] : () => all_statuses, }; var items_table = $("#" + tab_id + '_table').kohaTable({ @@ -474,9 +482,9 @@ } }, { - data: "me._status", + data: "", className: "status", - searchable: false, // FIXME We are losing the ability to search on the status + searchable: false, orderable: false, render: function (data, type, row, meta) { let nodes = ""; @@ -500,11 +508,11 @@ } if ( status == 'in_transit' ) { - nodes += '%s'.format(_("In transit from %s to %s since %s").format(escape_str(row.transfer._strings.from_library.str), escape_str(row.transfer._strings.to_library.str), $date(row.transfer.datesent))); - } - - if ( status == 'transit_pending' ) { - nodes += '%s'.format(_("Transit pending from %s to %s since %s").format(escape_str(row.transfer._strings.from_library.str), escape_str(row.transfer._strings.to_library.str), $date(row.transfer.daterequested))); + if ( row.transfer.datesent ) { + nodes += '%s'.format(_("In transit from %s to %s since %s").format(escape_str(row.transfer._strings.from_library.str), escape_str(row.transfer._strings.to_library.str), $date(row.transfer.datesent))); + } else { + nodes += '%s'.format(_("Transit pending from %s to %s since %s").format(escape_str(row.transfer._strings.from_library.str), escape_str(row.transfer._strings.to_library.str), $date(row.transfer.daterequested))); + } } if ( status == 'lost' ) { -- 2.39.5