From eed664b7d351ae9a22b0722b18d65dcff3fc185d Mon Sep 17 00:00:00 2001 From: Shadowghost Date: Tue, 28 Jul 2026 12:42:34 +0200 Subject: Fix played/unplayed filter for empty Series and BoxSets --- .../Item/BaseItemRepository.TranslateQuery.cs | 29 ++++++++++------------ 1 file changed, 13 insertions(+), 16 deletions(-) (limited to 'Jellyfin.Server.Implementations/Item/BaseItemRepository.TranslateQuery.cs') diff --git a/Jellyfin.Server.Implementations/Item/BaseItemRepository.TranslateQuery.cs b/Jellyfin.Server.Implementations/Item/BaseItemRepository.TranslateQuery.cs index 47f8a40b9c..6006bfb2bf 100644 --- a/Jellyfin.Server.Implementations/Item/BaseItemRepository.TranslateQuery.cs +++ b/Jellyfin.Server.Implementations/Item/BaseItemRepository.TranslateQuery.cs @@ -476,19 +476,14 @@ public sealed partial class BaseItemRepository var seriesTypeName = _itemTypeLookup.BaseItemKindNames[BaseItemKind.Series]; var boxSetTypeName = _itemTypeLookup.BaseItemKindNames[BaseItemKind.BoxSet]; - // Series: played = at least one episode AND all episodes played; unplayed = otherwise. - IQueryable playedSeriesIds = hasSeries - ? context.BaseItems - .AsNoTracking() - .Where(e => !e.IsFolder && !e.IsVirtualItem && e.SeriesId.HasValue) - .GroupBy(e => e.SeriesId!.Value) - .Where(g => !g.Any(e => !e.UserData!.Any(ud => ud.UserId == userId && ud.Played))) - .Select(g => g.Key) - : Enumerable.Empty().AsQueryable(); + // Series and BoxSets are matched by absence of an unplayed descendant rather than by + // "all descendants played". + var seriesEpisodes = context.BaseItems + .AsNoTracking() + .Where(e => !e.IsFolder && !e.IsVirtualItem); - // BoxSet: played = all children played. - IQueryable playedBoxSetIds = hasBoxSet - ? GetFullyPlayedFolderIdsQuery( + IQueryable unplayedBoxSetIds = hasBoxSet + ? GetFoldersWithUnplayedItemsQuery( context, baseQuery.Where(e => e.Type == boxSetTypeName).Select(e => e.Id), filter.User!) @@ -502,15 +497,17 @@ public sealed partial class BaseItemRepository if (isPlayed) { baseQuery = baseQuery.Where(e => - (e.Type == seriesTypeName && playedSeriesIds.Contains(e.Id)) - || (e.Type == boxSetTypeName && playedBoxSetIds.Contains(e.Id)) + (e.Type == seriesTypeName && !seriesEpisodes.Any(ep => ep.SeriesId == e.Id + && !ep.UserData!.Any(ud => ud.UserId == userId && ud.Played))) + || (e.Type == boxSetTypeName && !unplayedBoxSetIds.Contains(e.Id)) || (e.Type != seriesTypeName && e.Type != boxSetTypeName && playedItemIds.Contains(e.Id))); } else { baseQuery = baseQuery.Where(e => - (e.Type == seriesTypeName && !playedSeriesIds.Contains(e.Id)) - || (e.Type == boxSetTypeName && !playedBoxSetIds.Contains(e.Id)) + (e.Type == seriesTypeName && seriesEpisodes.Any(ep => ep.SeriesId == e.Id + && !ep.UserData!.Any(ud => ud.UserId == userId && ud.Played))) + || (e.Type == boxSetTypeName && unplayedBoxSetIds.Contains(e.Id)) || (e.Type != seriesTypeName && e.Type != boxSetTypeName && !playedItemIds.Contains(e.Id))); } } -- cgit v1.2.3