A dashboard widget lists what expires in the next two weeks, and a daily command reminds whoever edits it a week and a day ahead. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
84 lines
2.9 KiB
PHP
84 lines
2.9 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Support;
|
|
|
|
use App\Enums\CallStatus;
|
|
use App\Filament\Resources\Calls\CallResource;
|
|
use App\Models\Call;
|
|
use App\Models\Page;
|
|
use App\Models\Project;
|
|
use App\Models\Service;
|
|
use App\Models\User;
|
|
use Carbon\CarbonInterface;
|
|
use Illuminate\Database\Eloquent\Model;
|
|
use Illuminate\Support\Collection;
|
|
|
|
/**
|
|
* What is about to stop being true on the site: calls about to close, and
|
|
* published content about to come off by itself.
|
|
*
|
|
* Neither is a mistake — both were set on purpose — which is why the health
|
|
* check does not flag them. But a call that closes on Friday with nobody
|
|
* having replied, or a page that vanishes on the first of the month, is better
|
|
* known about a week ahead than found out after.
|
|
*/
|
|
final class Expiring
|
|
{
|
|
/**
|
|
* @return Collection<int, array{type: string, record: Model, title: string, event: string, at: CarbonInterface, url: string}>
|
|
*/
|
|
public static function within(int $days): Collection
|
|
{
|
|
$until = now()->addDays($days)->endOfDay();
|
|
$items = collect();
|
|
|
|
Call::query()
|
|
->where('status', CallStatus::Open)
|
|
->whereNotNull('closes_at')
|
|
->whereBetween('closes_at', [now()->startOfDay(), $until])
|
|
->get()
|
|
->each(fn (Call $call) => $items->push(self::item('calls', $call, 'closes', $call->closes_at)));
|
|
|
|
foreach (['pages' => Page::class, 'projects' => Project::class, 'services' => Service::class] as $type => $model) {
|
|
$model::query()
|
|
->published()
|
|
->whereNotNull('unpublish_at')
|
|
->whereBetween('unpublish_at', [now(), $until])
|
|
->get()
|
|
->each(fn (Model $record) => $items->push(self::item($type, $record, 'unpublishes', $record->unpublish_at)));
|
|
}
|
|
|
|
return $items->sortBy(fn (array $item) => $item['at']->getTimestamp())->values();
|
|
}
|
|
|
|
/**
|
|
* Only those this person may do something about.
|
|
*
|
|
* @param Collection<int, array<string, mixed>> $items
|
|
* @return Collection<int, array<string, mixed>>
|
|
*/
|
|
public static function for(Collection $items, User $user): Collection
|
|
{
|
|
return $items->filter(fn (array $item) => $user->can('update', $item['record']))->values();
|
|
}
|
|
|
|
/**
|
|
* @return array{type: string, record: Model, title: string, event: string, at: CarbonInterface, url: string}
|
|
*/
|
|
protected static function item(string $type, Model $record, string $event, CarbonInterface $at): array
|
|
{
|
|
return [
|
|
'type' => $type,
|
|
'record' => $record,
|
|
'title' => ContentTypes::title($record),
|
|
'event' => $event,
|
|
'at' => $at,
|
|
'url' => $record instanceof Call
|
|
? CallResource::getUrl('edit', ['record' => $record])
|
|
: ReviewTarget::url($record),
|
|
];
|
|
}
|
|
}
|