Розробка Workflow-системи публікації контенту (чернетка → модерація → публікація)
Workflow-система контенту — система управління життєвим циклом публікацій. Замість прямої публікації будь-яким користувачем — налаштований процес: чернетка → редактура → модерація → публікація → архівування. Критична для видань, корпоративних блогів та платформ із користувацьким контентом.
Статуси та переходи
чернетка → на рецензію → затверджено → опубліковано → архівовано
↑ ↓
└── відхилено ←───┘
└── потребує правки ←───── (частковий повернення)
content_states (
id, content_type, content_id,
status: draft | review | approved | published | rejected | archived | scheduled,
assigned_to (editor/moderator id),
comment, -- коментар при відхиленні
scheduled_at, -- дата публікації для scheduled
published_at, archived_at,
transitioned_by, transitioned_at
)
content_state_history (
id, content_type, content_id,
from_status, to_status,
changed_by, comment, changed_at
)
Визначення дозволених переходів
class ContentWorkflow
{
private array $transitions = [
'draft' => ['review'],
'review' => ['approved', 'rejected', 'revision_needed'],
'approved' => ['published', 'scheduled'],
'rejected' => ['draft'],
'revision_needed' => ['draft'],
'published'=> ['archived', 'draft'], // повернути в чернетку = депубліковано
'scheduled'=> ['published', 'draft']
];
private array $permissions = [
'draft → review' => 'content.submit_for_review',
'review → approved' => 'content.approve',
'review → rejected' => 'content.approve',
'approved → published' => 'content.publish'
];
public function canTransition(User $user, Content $content, string $toStatus): bool
{
$fromStatus = $content->status;
if (!in_array($toStatus, $this->transitions[$fromStatus] ?? [])) {
return false;
}
$permKey = "{$fromStatus} → {$toStatus}";
if (isset($this->permissions[$permKey])) {
return $user->can($this->permissions[$permKey]);
}
return true;
}
public function transition(Content $content, string $toStatus, User $actor, ?string $comment = null): void
{
if (!$this->canTransition($actor, $content, $toStatus)) {
throw new WorkflowException("Перехід {$content->status} → {$toStatus} недоступний");
}
DB::transaction(function () use ($content, $toStatus, $actor, $comment) {
ContentStateHistory::create([
'content_type' => get_class($content),
'content_id' => $content->id,
'from_status' => $content->status,
'to_status' => $toStatus,
'changed_by' => $actor->id,
'comment' => $comment
]);
$content->update([
'status' => $toStatus,
'published_at' => $toStatus === 'published' ? now() : $content->published_at
]);
event(new ContentStatusChanged($content, $toStatus, $actor, $comment));
});
}
}
Призначення рецензентів
// При відправленні на рецензію — автоматичне призначення вільного редактора
class AssignReviewer
{
public function assign(Content $content): User
{
$reviewer = User::where('role', 'editor')
->withCount(['assignedContent' => fn($q) => $q->where('status', 'review')])
->orderBy('assigned_content_count') // найменш завантажений
->first();
$content->update(['assigned_to' => $reviewer->id]);
$reviewer->notify(new ContentAssignedForReview($content));
return $reviewer;
}
}
Дедлайни та нагадування
// Задача за розкладом: знайти контент, що чекає рецензії більше X годин
$overdue = Content::where('status', 'review')
->where('submitted_for_review_at', '<', now()->subHours(24))
->get();
foreach ($overdue as $content) {
$content->assignedEditor?->notify(new ReviewOverdueNotification($content));
Notification::sendToRole('chief_editor', new EscalationNotification($content));
}
Запланована публікація
class PublishScheduledContent implements ShouldQueue
{
public function handle(): void
{
Content::where('status', 'scheduled')
->where('scheduled_at', '<=', now())
->each(function (Content $content) {
app(ContentWorkflow::class)->transition(
$content, 'published', User::find($content->created_by)
);
});
}
}
Задача запускається щоп'ять хвилин через планувальник.
Інтерфейс редакційної панелі
Колонки за статусами (Kanban-подібний вигляд) або список з фільтрами. Для кожного запису:
- Поточний статус та відповідальна особа
- Кнопки доступних переходів (залежать від ролі)
- Коментарі модератора
- Історія змін статусу
Термін розробки: 3–5 тижнів для повної workflow-системи з ролями, призначенням, дедлайнами та історією.







