>> portal => origin => state|unchecked|total => count */ public function distribution(): array { $rows = []; foreach ([Portal::Presseecho, Portal::Businessportal24] as $portal) { // Herkunft je Meldung erst in einer abgeleiteten Tabelle bestimmen, // dann darüber gruppieren — MySQL (only_full_group_by) lehnt den // korrelierten Ausdruck direkt im GROUP BY ab. $visible = DB::query() ->fromSub( PressRelease::query() ->withoutGlobalScope(PortalScope::class) ->visibleInArchive() ->forPortalValues([$portal->value, Portal::Both->value]) ->selectRaw('press_releases.id, '.self::ORIGIN_SQL.' as origin'), 'r' ) ->selectRaw('origin, count(*) as total') ->groupBy('origin') ->get(); $checked = DB::query() ->fromSub( $this->latestChecks($portal) ->join('press_releases', 'press_releases.id', '=', 'press_release_index_checks.press_release_id') ->whereNull('press_releases.deleted_at') ->selectRaw('press_release_index_checks.id, press_release_index_checks.state, '.self::ORIGIN_SQL.' as origin'), 'c' ) ->selectRaw('origin, state, count(*) as total') ->groupBy('origin', 'state') ->get(); foreach (['direct', 'api'] as $origin) { $bucket = ['total' => 0, 'unchecked' => 0]; foreach (IndexState::cases() as $state) { $bucket[$state->value] = 0; } $bucket['total'] = (int) ($visible->firstWhere('origin', $origin)->total ?? 0); $checkedTotal = 0; foreach ($checked->where('origin', $origin) as $row) { $bucket[(string) $row->state] = (int) $row->total; $checkedTotal += (int) $row->total; } $bucket['unchecked'] = max(0, $bucket['total'] - $checkedTotal); $rows[$portal->value][$origin] = $bucket; } } return $rows; } /** * Jüngste Prüfungen, neueste zuerst. * * @return Collection */ public function latest(int $limit = 100): Collection { return PressReleaseIndexCheck::query() ->with(['pressRelease' => fn ($query) => $query->withoutGlobalScopes()->select(['id', 'title', 'slug', 'published_at'])]) ->orderByDesc('checked_at') ->orderByDesc('id') ->limit($limit) ->get(); } public function lastCheckedAt(): ?Carbon { $value = PressReleaseIndexCheck::query()->max('checked_at'); return $value ? Carbon::parse((string) $value) : null; } /** * Jeweils jüngste Prüfung je Meldung auf einem Portal. * * @return Builder */ private function latestChecks(Portal $portal): Builder { $latest = PressReleaseIndexCheck::query() ->select('press_release_id', DB::raw('max(checked_at) as last_checked_at')) ->where('portal', $portal->value) ->groupBy('press_release_id'); return PressReleaseIndexCheck::query() ->joinSub($latest, 'latest', fn ($join) => $join ->on('latest.press_release_id', '=', 'press_release_index_checks.press_release_id') ->on('latest.last_checked_at', '=', 'press_release_index_checks.checked_at')) ->where('press_release_index_checks.portal', $portal->value); } }