> $sections * @return list */ public function blockersForSections(iterable $sections): array { $blockers = []; foreach ($sections as $section) { $required = (bool) ($section['required'] ?? false); $state = (string) ($section['completeness_state'] ?? TenantReviewCompletenessState::Missing->value); $title = (string) ($section['title'] ?? 'Review section'); if (! $required) { continue; } if ($state === TenantReviewCompletenessState::Missing->value) { $blockers[] = sprintf('%s is missing.', $title); } if ($state === TenantReviewCompletenessState::Stale->value) { $blockers[] = sprintf('%s is stale and must be refreshed before publication.', $title); } } return array_values(array_unique($blockers)); } /** * @param iterable> $sections */ public function completenessForSections(iterable $sections): TenantReviewCompletenessState { $states = collect($sections) ->map(static fn (array $section): string => (string) ($section['completeness_state'] ?? TenantReviewCompletenessState::Missing->value)) ->values(); if ($states->isEmpty()) { return TenantReviewCompletenessState::Missing; } if ($states->contains(TenantReviewCompletenessState::Missing->value)) { return TenantReviewCompletenessState::Missing; } if ($states->contains(TenantReviewCompletenessState::Stale->value)) { return TenantReviewCompletenessState::Stale; } if ($states->contains(TenantReviewCompletenessState::Partial->value)) { return TenantReviewCompletenessState::Partial; } return TenantReviewCompletenessState::Complete; } /** * @param iterable> $sections */ public function statusForSections(iterable $sections): TenantReviewStatus { return $this->blockersForSections($sections) === [] ? TenantReviewStatus::Ready : TenantReviewStatus::Draft; } /** * @return list */ public function blockersForReview(TenantReview $review): array { $sections = $review->relationLoaded('sections') ? $review->sections : $review->sections()->get(); return $this->blockersForSections($sections->map(static function ($section): array { return [ 'title' => (string) $section->title, 'required' => (bool) $section->required, 'completeness_state' => (string) $section->completeness_state, ]; })->all()); } public function canPublish(TenantReview $review): bool { if (! $review->isMutable()) { return false; } return $this->blockersForReview($review) === []; } public function canExport(TenantReview $review): bool { if (! in_array($review->statusEnum(), [ TenantReviewStatus::Ready, TenantReviewStatus::Published, ], true)) { return false; } return $this->blockersForReview($review) === []; } /** * @param iterable> $sections * @return array */ public function sectionStateCounts(iterable $sections): array { $counts = collect($sections) ->groupBy(static fn (array $section): string => (string) ($section['completeness_state'] ?? TenantReviewCompletenessState::Missing->value)) ->map(static fn (Collection $group): int => $group->count()); return [ 'complete' => (int) ($counts[TenantReviewCompletenessState::Complete->value] ?? 0), 'partial' => (int) ($counts[TenantReviewCompletenessState::Partial->value] ?? 0), 'missing' => (int) ($counts[TenantReviewCompletenessState::Missing->value] ?? 0), 'stale' => (int) ($counts[TenantReviewCompletenessState::Stale->value] ?? 0), ]; } }