From 669637716b25953164a8294c199e2863b907ea48 Mon Sep 17 00:00:00 2001 From: Jake Deery Date: Mon, 1 Dec 2025 13:06:40 +0000 Subject: [PATCH] Bug 14962: On Display code for C4 modules This patch adds code for the On Display module, found in the C4 modules. Please see the test files commit for instructions on how to test this bundle of patches. --- C4/Circulation.pm | 88 +++++++++++++++++++++++++++++++++++++++++++---- C4/Search.pm | 55 +++++++++++++++++++++++++++-- 2 files changed, 133 insertions(+), 10 deletions(-) diff --git a/C4/Circulation.pm b/C4/Circulation.pm index 0b2b57e3754..3d774da3614 100644 --- a/C4/Circulation.pm +++ b/C4/Circulation.pm @@ -123,6 +123,7 @@ use Koha::Exceptions::Checkout; use Koha::Plugins; use Koha::Recalls; use Koha::Library::Hours; +use Koha::DisplayItems; use Carp qw( carp ); use List::MoreUtils qw( any ); use Scalar::Util qw( looks_like_number blessed ); @@ -2753,6 +2754,27 @@ sub AddReturn { my $indexer = Koha::SearchEngine::Indexer->new( { index => $Koha::SearchEngine::BIBLIOS_INDEX } ); $indexer->index_records( $item->biblionumber, "specialUpdate", "biblioserver" ); + if ( my $display_item = $item->active_display_item ) { + my $display = $display_item->display; + my $return_over = $display->display_return_over; + my $should_remove = 0; + + if ( $return_over eq 'yes - any library' ) { + $should_remove = 1; + } elsif ( $return_over eq 'yes - except at home library' ) { + my $homebranch = $item->homebranch; + $should_remove = 1 if $branch ne $homebranch; + } + + if ($should_remove) { + $display_item->delete; + $messages->{RemovedFromDisplay} = { + display_id => $display->display_id, + display_name => $display->display_name, + }; + } + } + if ( $doreturn and $issue ) { my $checkin = Koha::Old::Checkouts->find( $issue->id ); @@ -4778,8 +4800,10 @@ sub GetAgeRestriction { sub GetPendingOnSiteCheckouts { my $dbh = C4::Context->dbh; - return $dbh->selectall_arrayref( - q| + + my $use_display_module = C4::Context->preference('UseDisplayModule'); + + my $sql = q| SELECT items.barcode, items.biblionumber, @@ -4795,14 +4819,64 @@ sub GetPendingOnSiteCheckouts { borrowers.firstname, borrowers.surname, borrowers.cardnumber, - borrowers.borrowernumber + borrowers.borrowernumber|; + + if ($use_display_module) { + $sql .= q|, + COALESCE(active_display.display_location, items.location) AS effective_location, + active_display.display_name, + active_display.display_location AS raw_display_location|; + } else { + $sql .= q|, + items.location AS effective_location, + NULL AS display_name, + NULL AS raw_display_location|; + } + + $sql .= q| FROM items LEFT JOIN issues ON items.itemnumber = issues.itemnumber LEFT JOIN biblio ON items.biblionumber = biblio.biblionumber - LEFT JOIN borrowers ON issues.borrowernumber = borrowers.borrowernumber - WHERE issues.onsite_checkout = 1 - |, { Slice => {} } - ); + LEFT JOIN borrowers ON issues.borrowernumber = borrowers.borrowernumber|; + + if ($use_display_module) { + $sql .= q| + LEFT JOIN ( + SELECT + di.itemnumber, + d.display_location, + d.display_name, + ROW_NUMBER() OVER (PARTITION BY di.itemnumber ORDER BY di.date_added DESC) as rn + FROM display_items di + JOIN displays d ON di.display_id = d.display_id + WHERE d.enabled = 1 + AND (d.start_date IS NULL OR d.start_date <= CURDATE()) + AND (d.end_date IS NULL OR d.end_date >= CURDATE()) + AND (di.date_remove IS NULL OR di.date_remove >= CURDATE()) + ) active_display ON items.itemnumber = active_display.itemnumber AND active_display.rn = 1|; + } + + $sql .= q| + WHERE issues.onsite_checkout = 1|; + + my $results = $dbh->selectall_arrayref( $sql, { Slice => {} } ); + + # Add effective location description for each item + foreach my $checkout (@$results) { + if ( $checkout->{display_name} && !$checkout->{raw_display_location} ) { + + # Item is on display with no specific display location + $checkout->{effective_location_description} = "DISPLAY: " . $checkout->{display_name}; + } else { + + # Use AuthorisedValues to get effective location description + my $av = Koha::AuthorisedValues->get_description_by_koha_field( + { kohafield => 'items.location', authorised_value => $checkout->{effective_location} } ); + $checkout->{effective_location_description} = $av->{lib} || ''; + } + } + + return $results; } =head2 GetTopIssues diff --git a/C4/Search.pm b/C4/Search.pm index 5d6f3c6e33d..cbdb20c6c7b 100644 --- a/C4/Search.pm +++ b/C4/Search.pm @@ -46,6 +46,8 @@ use C4::XSLT qw( XSLTParse4Display ); use C4::Reserves qw( GetReserveStatus ); use C4::Charset qw( SetUTF8Flag ); use Koha::AuthorisedValues; +use Koha::Displays; +use Koha::Items; use Koha::ItemTypes; use Koha::Libraries; use Koha::Logger; @@ -1687,6 +1689,34 @@ sub searchResults { ) }; + # Cache for display lookups to avoid repeated queries + my $display_cache = {}; + + # Helper function to get effective location for search results + my $get_effective_location_display = sub { + my ($item) = @_; + return $shelflocations->{ $item->{location} } unless C4::Context->preference('UseDisplayModule'); + + my $item_obj = Koha::Items->find( $item->{itemnumber} ); + return $shelflocations->{ $item->{location} } unless $item_obj; + + my $effective_loc = $item_obj->effective_location; + + # If it starts with "DISPLAY:", validate against displays table + if ( $effective_loc =~ /^DISPLAY:\s*(.+)$/ ) { + my $display_name = $1; + + # Use cache to avoid repeated database queries + if ( !exists $display_cache->{$display_name} ) { + my $display = Koha::Displays->search( { display_name => $display_name } )->next; + $display_cache->{$display_name} = $display ? $display->display_name : undef; + } + return $display_cache->{$display_name} ? "DISPLAY: " . $display_cache->{$display_name} : $effective_loc; + } else { + return $shelflocations->{$effective_loc}; + } + }; + # get notforloan authorised value list (see $shelflocations FIXME) my $av = Koha::MarcSubfieldStructures->search( { @@ -1891,6 +1921,22 @@ sub searchResults { $item->{'branchcode'} = $item->{$otherbranch}; } + # items on display + my @displays; + my @display_items = Koha::DisplayItems->search( + { itemnumber => $item->{itemnumber} }, + { + order_by => { '-desc' => 'date_added' }, + } + )->as_list; + + foreach my $display_item (@display_items) { + push @displays, $display_item->display; + } + + $item->{displays} = \@displays + unless $#displays < 0; + my $prefix = ( $item->{$hbranch} ? $item->{$hbranch} . '--' : q{} ) . ( $item->{location} ? $item->{location} : q{} ) @@ -1912,7 +1958,7 @@ sub searchResults { $onloan_items->{$key}->{count}++ if $item->{$hbranch}; $onloan_items->{$key}->{branchname} = $item->{branchname}; $onloan_items->{$key}->{branchcode} = $item->{branchcode}; - $onloan_items->{$key}->{location} = $shelflocations->{ $item->{location} } if $item->{location}; + $onloan_items->{$key}->{location} = $get_effective_location_display->($item) if $item->{location}; $onloan_items->{$key}->{itemcallnumber} = $item->{itemcallnumber}; $onloan_items->{$key}->{description} = $item->{description}; $onloan_items->{$key}->{imageurl} = getitemtypeimagelocation( @@ -1921,6 +1967,7 @@ sub searchResults { ); $onloan_items->{$key}->{collectioncode} = GetAuthorisedValueDesc( '', '', $item->{ccode}, '', '', 'CCODE' ); + $onloan_items->{$key}->{displays} = $item->{displays}; # if something's checked out and lost, mark it as 'long overdue' if ( $item->{itemlost} ) { @@ -2030,7 +2077,7 @@ sub searchResults { GetAuthorisedValueDesc( '', '', $item->{notforloan}, '', '', $notforloan_authorised_value ) if $notforloan_authorised_value and $item->{notforloan}; $other_items->{$key}->{count}++ if $item->{$hbranch}; - $other_items->{$key}->{location} = $shelflocations->{ $item->{location} } if $item->{location}; + $other_items->{$key}->{location} = $get_effective_location_display->($item) if $item->{location}; $other_items->{$key}->{description} = $item->{description}; $other_items->{$key}->{imageurl} = getitemtypeimagelocation( $search_context->{'interface'}, @@ -2038,6 +2085,7 @@ sub searchResults { ); $other_items->{$key}->{collectioncode} = GetAuthorisedValueDesc( '', '', $item->{ccode}, '', '', 'CCODE' ); + $other_items->{$key}->{displays} = $item->{displays}; } # item is available @@ -2051,7 +2099,7 @@ sub searchResults { } $available_items->{$prefix}->{branchavailablecount} = $branch_available_count; $available_items->{$prefix}->{branchcode} = $item->{branchcode}; - $available_items->{$prefix}->{location} = $shelflocations->{ $item->{location} } + $available_items->{$prefix}->{location} = $get_effective_location_display->($item) if $item->{location}; $available_items->{$prefix}->{imageurl} = getitemtypeimagelocation( $search_context->{'interface'}, @@ -2059,6 +2107,7 @@ sub searchResults { ); $available_items->{$prefix}->{collectioncode} = GetAuthorisedValueDesc( '', '', $item->{ccode}, '', '', 'CCODE' ); + $available_items->{$prefix}->{displays} = $item->{displays}; } } } # notforloan, item level and biblioitem level -- 2.50.1 (Apple Git-155)