changes
This commit is contained in:
@@ -0,0 +1,13 @@
|
||||
<?php
|
||||
|
||||
namespace App\Containers\Dashboard\Actions\AcademicJournals;
|
||||
|
||||
use App\Containers\Science\Models\AcademicJournal;
|
||||
|
||||
class CreateAcademicJournalAction
|
||||
{
|
||||
public function run(array $data): AcademicJournal
|
||||
{
|
||||
return AcademicJournal::create($data);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
<?php
|
||||
|
||||
namespace App\Containers\Dashboard\Actions\AcademicJournals;
|
||||
|
||||
use App\Containers\Science\Models\AcademicJournal;
|
||||
|
||||
class DeleteAcademicJournalAction
|
||||
{
|
||||
public function run(AcademicJournal $journal): bool
|
||||
{
|
||||
return $journal->delete();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
namespace App\Containers\Dashboard\Actions\AcademicJournals;
|
||||
|
||||
use App\Containers\Science\Models\AcademicJournal;
|
||||
|
||||
class ListAcademicJournalsAction
|
||||
{
|
||||
public function run(array $filters = []): array
|
||||
{
|
||||
$query = AcademicJournal::query();
|
||||
|
||||
// Поиск по названию
|
||||
if (!empty($filters['search'])) {
|
||||
$query->where('title', 'like', '%' . $filters['search'] . '%');
|
||||
}
|
||||
|
||||
$journals = $query->orderBy('created_at', 'desc')->paginate(20)->withQueryString();
|
||||
|
||||
return [
|
||||
'journals' => $journals,
|
||||
'filters' => $filters,
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
|
||||
namespace App\Containers\Dashboard\Actions\AcademicJournals;
|
||||
|
||||
use App\Containers\Science\Models\AcademicJournal;
|
||||
|
||||
class UpdateAcademicJournalAction
|
||||
{
|
||||
public function run(AcademicJournal $journal, array $data): AcademicJournal
|
||||
{
|
||||
$journal->update($data);
|
||||
return $journal->fresh();
|
||||
}
|
||||
}
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
<?php
|
||||
|
||||
namespace App\Containers\Dashboard\Actions\AdditionalEducations\Categories;
|
||||
|
||||
use App\Containers\AdditionalEducation\Models\AdditionalEducationCategory;
|
||||
|
||||
class CreateCategoryAction
|
||||
{
|
||||
public function run(array $data): AdditionalEducationCategory
|
||||
{
|
||||
return AdditionalEducationCategory::create($data);
|
||||
}
|
||||
}
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
<?php
|
||||
|
||||
namespace App\Containers\Dashboard\Actions\AdditionalEducations\Categories;
|
||||
|
||||
use App\Containers\AdditionalEducation\Models\AdditionalEducationCategory;
|
||||
|
||||
class DeleteCategoryAction
|
||||
{
|
||||
public function run(AdditionalEducationCategory $category): bool
|
||||
{
|
||||
return $category->delete();
|
||||
}
|
||||
}
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
namespace App\Containers\Dashboard\Actions\AdditionalEducations\Categories;
|
||||
|
||||
use App\Containers\AdditionalEducation\Models\AdditionalEducationCategory;
|
||||
use App\Containers\AdditionalEducation\Models\DirectionAdditionalEducation;
|
||||
|
||||
class ListCategoriesAction
|
||||
{
|
||||
public function run(array $filters = []): array
|
||||
{
|
||||
$query = AdditionalEducationCategory::with(['direction']);
|
||||
|
||||
// Фильтр по направлению
|
||||
if (!empty($filters['direction_id'])) {
|
||||
$query->where('dir_addit_educat_id', $filters['direction_id']);
|
||||
}
|
||||
|
||||
// Фильтр по активности
|
||||
if (isset($filters['is_active']) && $filters['is_active'] !== '') {
|
||||
$query->where('is_active', filter_var($filters['is_active'], FILTER_VALIDATE_BOOLEAN));
|
||||
}
|
||||
|
||||
// Поиск по названию
|
||||
if (!empty($filters['search'])) {
|
||||
$search = $filters['search'];
|
||||
$query->where('title', 'like', '%' . $search . '%');
|
||||
}
|
||||
|
||||
$categories = $query->orderBy('title')->paginate(20)->withQueryString();
|
||||
|
||||
return [
|
||||
'categories' => $categories,
|
||||
'filters' => $filters,
|
||||
'directions' => DirectionAdditionalEducation::where('is_active', true)
|
||||
->orderBy('title')
|
||||
->get(['id', 'title']),
|
||||
];
|
||||
}
|
||||
}
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
|
||||
namespace App\Containers\Dashboard\Actions\AdditionalEducations\Categories;
|
||||
|
||||
use App\Containers\AdditionalEducation\Models\AdditionalEducationCategory;
|
||||
|
||||
class UpdateCategoryAction
|
||||
{
|
||||
public function run(AdditionalEducationCategory $category, array $data): AdditionalEducationCategory
|
||||
{
|
||||
$category->update($data);
|
||||
return $category;
|
||||
}
|
||||
}
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
namespace App\Containers\Dashboard\Actions\AdditionalEducations;
|
||||
|
||||
use App\Containers\AdditionalEducation\Models\AdditionalEducation;
|
||||
use App\Containers\Dashboard\Tasks\AdditionalEducations\GenerateSearchDataTask;
|
||||
|
||||
class CreateAdditionalEducationAction
|
||||
{
|
||||
public function __construct(
|
||||
private readonly GenerateSearchDataTask $generateSearchDataTask,
|
||||
) {}
|
||||
|
||||
public function run(array $data): AdditionalEducation
|
||||
{
|
||||
$data['search_data'] = $this->generateSearchDataTask->run($data['content'] ?? []);
|
||||
|
||||
return AdditionalEducation::create($data);
|
||||
}
|
||||
}
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
<?php
|
||||
|
||||
namespace App\Containers\Dashboard\Actions\AdditionalEducations;
|
||||
|
||||
use App\Containers\AdditionalEducation\Models\AdditionalEducation;
|
||||
|
||||
class DeleteAdditionalEducationAction
|
||||
{
|
||||
public function run(AdditionalEducation $education): bool
|
||||
{
|
||||
return $education->delete();
|
||||
}
|
||||
}
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
<?php
|
||||
|
||||
namespace App\Containers\Dashboard\Actions\AdditionalEducations\Directions;
|
||||
|
||||
use App\Containers\AdditionalEducation\Models\DirectionAdditionalEducation;
|
||||
|
||||
class CreateDirectionAction
|
||||
{
|
||||
public function run(array $data): DirectionAdditionalEducation
|
||||
{
|
||||
return DirectionAdditionalEducation::create($data);
|
||||
}
|
||||
}
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
<?php
|
||||
|
||||
namespace App\Containers\Dashboard\Actions\AdditionalEducations\Directions;
|
||||
|
||||
use App\Containers\AdditionalEducation\Models\DirectionAdditionalEducation;
|
||||
|
||||
class DeleteDirectionAction
|
||||
{
|
||||
public function run(DirectionAdditionalEducation $direction): bool
|
||||
{
|
||||
return $direction->delete();
|
||||
}
|
||||
}
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
namespace App\Containers\Dashboard\Actions\AdditionalEducations\Directions;
|
||||
|
||||
use App\Containers\AdditionalEducation\Models\DirectionAdditionalEducation;
|
||||
|
||||
class ListDirectionsAction
|
||||
{
|
||||
public function run(array $filters = []): array
|
||||
{
|
||||
$query = DirectionAdditionalEducation::query();
|
||||
|
||||
// Фильтр по активности
|
||||
if (isset($filters['is_active']) && $filters['is_active'] !== '') {
|
||||
$query->where('is_active', filter_var($filters['is_active'], FILTER_VALIDATE_BOOLEAN));
|
||||
}
|
||||
|
||||
// Поиск по названию
|
||||
if (!empty($filters['search'])) {
|
||||
$search = $filters['search'];
|
||||
$query->where('title', 'like', '%' . $search . '%');
|
||||
}
|
||||
|
||||
$directions = $query->orderBy('title')->paginate(20)->withQueryString();
|
||||
|
||||
return [
|
||||
'directions' => $directions,
|
||||
'filters' => $filters,
|
||||
];
|
||||
}
|
||||
}
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
|
||||
namespace App\Containers\Dashboard\Actions\AdditionalEducations\Directions;
|
||||
|
||||
use App\Containers\AdditionalEducation\Models\DirectionAdditionalEducation;
|
||||
|
||||
class UpdateDirectionAction
|
||||
{
|
||||
public function run(DirectionAdditionalEducation $direction, array $data): DirectionAdditionalEducation
|
||||
{
|
||||
$direction->update($data);
|
||||
return $direction;
|
||||
}
|
||||
}
|
||||
+55
@@ -0,0 +1,55 @@
|
||||
<?php
|
||||
|
||||
namespace App\Containers\Dashboard\Actions\AdditionalEducations;
|
||||
|
||||
use App\Containers\AdditionalEducation\Models\AdditionalEducation;
|
||||
use App\Containers\AdditionalEducation\Models\AdditionalEducationCategory;
|
||||
use App\Ship\Enums\Education\FormEducation;
|
||||
|
||||
class ListAdditionalEducationsAction
|
||||
{
|
||||
public function run(array $filters = []): array
|
||||
{
|
||||
$query = AdditionalEducation::with(['category']);
|
||||
|
||||
// Фильтр по категории
|
||||
if (!empty($filters['category_id'])) {
|
||||
$query->where('category_id', $filters['category_id']);
|
||||
}
|
||||
|
||||
// Фильтр по форме обучения
|
||||
if (!empty($filters['form_education'])) {
|
||||
$query->where('form_education', $filters['form_education']);
|
||||
}
|
||||
|
||||
// Фильтр по активности
|
||||
if (isset($filters['is_active']) && $filters['is_active'] !== '') {
|
||||
$query->where('is_active', filter_var($filters['is_active'], FILTER_VALIDATE_BOOLEAN));
|
||||
}
|
||||
|
||||
// Поиск по названию или целевой аудитории
|
||||
if (!empty($filters['search'])) {
|
||||
$search = $filters['search'];
|
||||
$query->where(function ($q) use ($search) {
|
||||
$q->where('title', 'like', '%' . $search . '%')
|
||||
->orWhere('target_group', 'like', '%' . $search . '%');
|
||||
});
|
||||
}
|
||||
|
||||
$educations = $query->orderBy('title')->paginate(20)->withQueryString();
|
||||
|
||||
return [
|
||||
'educations' => $educations,
|
||||
'filters' => $filters,
|
||||
'categories' => AdditionalEducationCategory::where('is_active', true)
|
||||
->orderBy('title')
|
||||
->get(['id', 'title']),
|
||||
'educationForms' => array_map(fn($form) => [
|
||||
'value' => $form->value,
|
||||
'label' => $form->getLabel(),
|
||||
'color' => $form->getColor(),
|
||||
'name' => $form->name,
|
||||
], FormEducation::cases()),
|
||||
];
|
||||
}
|
||||
}
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
namespace App\Containers\Dashboard\Actions\AdditionalEducations;
|
||||
|
||||
use App\Containers\AdditionalEducation\Models\AdditionalEducation;
|
||||
use App\Containers\Dashboard\Tasks\AdditionalEducations\GenerateSearchDataTask;
|
||||
|
||||
class UpdateAdditionalEducationAction
|
||||
{
|
||||
public function __construct(
|
||||
private readonly GenerateSearchDataTask $generateSearchDataTask,
|
||||
) {}
|
||||
|
||||
public function run(AdditionalEducation $education, array $data): AdditionalEducation
|
||||
{
|
||||
if (isset($data['content'])) {
|
||||
$data['search_data'] = $this->generateSearchDataTask->run($data['content']);
|
||||
}
|
||||
|
||||
$education->update($data);
|
||||
|
||||
return $education;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
<?php
|
||||
|
||||
namespace App\Containers\Dashboard\Actions\AdmissionCampaigns;
|
||||
|
||||
use App\Containers\Education\Models\AdmissionCampaign;
|
||||
|
||||
class CreateAdmissionCampaignAction
|
||||
{
|
||||
public function run(array $data): AdmissionCampaign
|
||||
{
|
||||
return AdmissionCampaign::create($data);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
<?php
|
||||
|
||||
namespace App\Containers\Dashboard\Actions\AdmissionCampaigns;
|
||||
|
||||
use App\Containers\Education\Models\AdmissionCampaign;
|
||||
|
||||
class DeleteAdmissionCampaignAction
|
||||
{
|
||||
public function run(AdmissionCampaign $campaign): bool
|
||||
{
|
||||
return $campaign->delete();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
<?php
|
||||
|
||||
namespace App\Containers\Dashboard\Actions\AdmissionCampaigns;
|
||||
|
||||
use App\Containers\Education\Models\AdmissionCampaign;
|
||||
use App\Ship\Enums\Education\AdmissionCampaignStatus;
|
||||
|
||||
class ListAdmissionCampaignsAction
|
||||
{
|
||||
public function run(array $filters = []): array
|
||||
{
|
||||
$query = AdmissionCampaign::query();
|
||||
|
||||
// Фильтр по статусу
|
||||
if (!empty($filters['status'])) {
|
||||
$query->where('status', $filters['status']);
|
||||
}
|
||||
|
||||
// Фильтр по учебному году
|
||||
if (!empty($filters['academic_year'])) {
|
||||
$query->where('academic_year', $filters['academic_year']);
|
||||
}
|
||||
|
||||
// Поиск по названию
|
||||
if (!empty($filters['search'])) {
|
||||
$search = $filters['search'];
|
||||
$query->where('name', 'like', '%' . $search . '%');
|
||||
}
|
||||
|
||||
$campaigns = $query->orderBy('academic_year', 'desc')->paginate(20)->withQueryString();
|
||||
|
||||
return [
|
||||
'campaigns' => $campaigns,
|
||||
'filters' => $filters,
|
||||
'statuses' => array_map(fn($status) => [
|
||||
'value' => $status->value,
|
||||
'label' => $status->getLabel(),
|
||||
'color' => $status->getColor(),
|
||||
], AdmissionCampaignStatus::cases()),
|
||||
'academicYears' => $this->generateAcademicYears(),
|
||||
];
|
||||
}
|
||||
|
||||
private function generateAcademicYears(): array
|
||||
{
|
||||
$currentYear = (int) date('Y') - 5;
|
||||
$yearsAhead = 10;
|
||||
$academicYears = [];
|
||||
|
||||
for ($i = 0; $i < $yearsAhead; $i++) {
|
||||
$startYear = $currentYear + $i;
|
||||
$endYear = $startYear + 1;
|
||||
$academicYears[] = "{$startYear}/{$endYear}";
|
||||
}
|
||||
|
||||
return $academicYears;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
|
||||
namespace App\Containers\Dashboard\Actions\AdmissionCampaigns;
|
||||
|
||||
use App\Containers\Education\Models\AdmissionCampaign;
|
||||
|
||||
class UpdateAdmissionCampaignAction
|
||||
{
|
||||
public function run(AdmissionCampaign $campaign, array $data): AdmissionCampaign
|
||||
{
|
||||
$campaign->update($data);
|
||||
return $campaign;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
<?php
|
||||
|
||||
namespace App\Containers\Dashboard\Actions\AdmissionPlans;
|
||||
|
||||
use App\Containers\Education\Models\AdmissionPlan;
|
||||
|
||||
class CreateAdmissionPlanAction
|
||||
{
|
||||
public function run(array $data): AdmissionPlan
|
||||
{
|
||||
return AdmissionPlan::create($data);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
<?php
|
||||
|
||||
namespace App\Containers\Dashboard\Actions\AdmissionPlans;
|
||||
|
||||
use App\Containers\Education\Models\AdmissionPlan;
|
||||
|
||||
class DeleteAdmissionPlanAction
|
||||
{
|
||||
public function run(AdmissionPlan $plan): bool
|
||||
{
|
||||
return $plan->delete();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
<?php
|
||||
|
||||
namespace App\Containers\Dashboard\Actions\AdmissionPlans;
|
||||
|
||||
use App\Containers\Education\Models\AdmissionPlan;
|
||||
use App\Containers\Education\Models\AdmissionCampaign;
|
||||
use App\Containers\Education\Models\EducationalProgram;
|
||||
use App\Ship\Enums\Education\EducationalProgramStatus;
|
||||
|
||||
class ListAdmissionPlansAction
|
||||
{
|
||||
public function run(array $filters = []): array
|
||||
{
|
||||
$query = AdmissionPlan::with(['educationalProgram', 'admissionCampaign']);
|
||||
|
||||
// Фильтр по приемной кампании
|
||||
if (!empty($filters['admission_campaigns_id'])) {
|
||||
$query->where('admission_campaigns_id', $filters['admission_campaigns_id']);
|
||||
}
|
||||
|
||||
// Фильтр по образовательной программе
|
||||
if (!empty($filters['educational_programs_id'])) {
|
||||
$query->where('educational_programs_id', $filters['educational_programs_id']);
|
||||
}
|
||||
|
||||
$plans = $query->orderBy('id', 'desc')->paginate(20)->withQueryString();
|
||||
|
||||
return [
|
||||
'plans' => $plans,
|
||||
'filters' => $filters,
|
||||
'admissionCampaigns' => AdmissionCampaign::orderBy('name')->get(['id', 'name', 'academic_year']),
|
||||
'educationalPrograms' => EducationalProgram::whereIn('status', [
|
||||
EducationalProgramStatus::PUBLISHED,
|
||||
EducationalProgramStatus::IN_PROGRESS
|
||||
])->orderBy('name')->get(['id', 'name']),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
|
||||
namespace App\Containers\Dashboard\Actions\AdmissionPlans;
|
||||
|
||||
use App\Containers\Education\Models\AdmissionPlan;
|
||||
|
||||
class UpdateAdmissionPlanAction
|
||||
{
|
||||
public function run(AdmissionPlan $plan, array $data): AdmissionPlan
|
||||
{
|
||||
$plan->update($data);
|
||||
return $plan;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
namespace App\Containers\Dashboard\Actions\Categories;
|
||||
|
||||
use App\Containers\Article\Models\Category;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class CreateCategoryAction
|
||||
{
|
||||
public function run(array $data): Category
|
||||
{
|
||||
$data['slug'] = Str::slug($data['title']);
|
||||
|
||||
return Category::create($data);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
<?php
|
||||
|
||||
namespace App\Containers\Dashboard\Actions\Categories;
|
||||
|
||||
use App\Containers\Article\Models\Category;
|
||||
|
||||
class DeleteCategoryAction
|
||||
{
|
||||
public function run(Category $category): bool
|
||||
{
|
||||
return $category->delete();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
namespace App\Containers\Dashboard\Actions\Categories;
|
||||
|
||||
use App\Containers\Article\Models\Category;
|
||||
|
||||
class ListCategoriesAction
|
||||
{
|
||||
public function run(array $filters = []): array
|
||||
{
|
||||
$query = Category::query();
|
||||
|
||||
// Поиск по названию
|
||||
if (!empty($filters['search'])) {
|
||||
$query->where('title', 'like', '%' . $filters['search'] . '%');
|
||||
}
|
||||
|
||||
// Фильтр по статусу
|
||||
if (isset($filters['is_active']) && $filters['is_active'] !== '') {
|
||||
$query->where('is_active', (bool) $filters['is_active']);
|
||||
}
|
||||
|
||||
$categories = $query->orderBy('title')->paginate(20)->withQueryString();
|
||||
|
||||
return [
|
||||
'categories' => $categories,
|
||||
'filters' => $filters,
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
namespace App\Containers\Dashboard\Actions\Categories;
|
||||
|
||||
use App\Containers\Article\Models\Category;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class UpdateCategoryAction
|
||||
{
|
||||
public function run(Category $category, array $data): Category
|
||||
{
|
||||
if (isset($data['title'])) {
|
||||
$data['slug'] = Str::slug($data['title']);
|
||||
}
|
||||
|
||||
$category->update($data);
|
||||
return $category->fresh();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
<?php
|
||||
|
||||
namespace App\Containers\Dashboard\Actions\ContactWidgets;
|
||||
|
||||
use App\Containers\Widget\Models\ContactWidget;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class CreateContactWidgetAction
|
||||
{
|
||||
public function run(array $data): ContactWidget
|
||||
{
|
||||
$data['slug'] = $data['slug'] ?? Str::slug($data['title']);
|
||||
$data['is_active'] = $data['is_active'] ?? true;
|
||||
|
||||
return ContactWidget::create($data);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
<?php
|
||||
|
||||
namespace App\Containers\Dashboard\Actions\ContactWidgets;
|
||||
|
||||
use App\Containers\Widget\Models\ContactWidget;
|
||||
|
||||
class DeleteContactWidgetAction
|
||||
{
|
||||
public function run(ContactWidget $widget): bool
|
||||
{
|
||||
return $widget->delete();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
namespace App\Containers\Dashboard\Actions\ContactWidgets;
|
||||
|
||||
use App\Containers\Widget\Models\ContactWidget;
|
||||
|
||||
class ListContactWidgetsAction
|
||||
{
|
||||
public function run(array $filters = []): array
|
||||
{
|
||||
$query = ContactWidget::query();
|
||||
|
||||
// Поиск по названию
|
||||
if (!empty($filters['search'])) {
|
||||
$query->where('title', 'like', '%' . $filters['search'] . '%');
|
||||
}
|
||||
|
||||
// Фильтр по статусу
|
||||
if (isset($filters['is_active']) && $filters['is_active'] !== '') {
|
||||
$query->where('is_active', (bool) $filters['is_active']);
|
||||
}
|
||||
|
||||
$widgets = $query->orderByDesc('created_at')->paginate(20)->withQueryString();
|
||||
|
||||
return [
|
||||
'widgets' => $widgets,
|
||||
'filters' => $filters,
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
<?php
|
||||
|
||||
namespace App\Containers\Dashboard\Actions\ContactWidgets;
|
||||
|
||||
use App\Containers\Widget\Models\ContactWidget;
|
||||
|
||||
class UpdateContactWidgetAction
|
||||
{
|
||||
public function run(ContactWidget $widget, array $data): ContactWidget
|
||||
{
|
||||
$widget->update($data);
|
||||
|
||||
return $widget;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
namespace App\Containers\Dashboard\Actions\CustomForms;
|
||||
|
||||
use App\Containers\Widget\Models\CustomForm;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class CreateCustomFormAction
|
||||
{
|
||||
public function run(array $data): CustomForm
|
||||
{
|
||||
$data['form_id'] = $data['form_id'] ?? Str::slug($data['title']) . time();
|
||||
$data['status'] = $data['status'] ?? 'published';
|
||||
$data['settings'] = $data['settings'] ?? [];
|
||||
$data['mail_settings'] = $data['mail_settings'] ?? [];
|
||||
$data['columns'] = $data['columns'] ?? [];
|
||||
|
||||
return CustomForm::create($data);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
<?php
|
||||
|
||||
namespace App\Containers\Dashboard\Actions\CustomForms;
|
||||
|
||||
use App\Containers\Widget\Models\CustomForm;
|
||||
|
||||
class DeleteCustomFormAction
|
||||
{
|
||||
public function run(CustomForm $form): bool
|
||||
{
|
||||
return $form->delete();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
<?php
|
||||
|
||||
namespace App\Containers\Dashboard\Actions\CustomForms;
|
||||
|
||||
use App\Containers\Widget\Models\CustomFormResponse;
|
||||
|
||||
class DeleteFormResponseAction
|
||||
{
|
||||
public function run(CustomFormResponse $response): bool
|
||||
{
|
||||
return $response->delete();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
namespace App\Containers\Dashboard\Actions\CustomForms;
|
||||
|
||||
use App\Containers\Widget\Models\CustomForm;
|
||||
|
||||
class ListCustomFormsAction
|
||||
{
|
||||
public function run(array $filters = []): array
|
||||
{
|
||||
$query = CustomForm::query();
|
||||
|
||||
// Поиск по названию
|
||||
if (!empty($filters['search'])) {
|
||||
$query->where('title', 'like', '%' . $filters['search'] . '%');
|
||||
}
|
||||
|
||||
// Фильтр по статусу
|
||||
if (!empty($filters['status'])) {
|
||||
$query->where('status', $filters['status']);
|
||||
}
|
||||
|
||||
$forms = $query->orderByDesc('created_at')->paginate(20)->withQueryString();
|
||||
|
||||
return [
|
||||
'forms' => $forms,
|
||||
'filters' => $filters,
|
||||
'statuses' => [
|
||||
['value' => 'published', 'label' => 'Опубликовано'],
|
||||
['value' => 'hidden', 'label' => 'Скрыто'],
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
<?php
|
||||
|
||||
namespace App\Containers\Dashboard\Actions\CustomForms;
|
||||
|
||||
use App\Containers\Widget\Models\CustomForm;
|
||||
|
||||
class ListFormResponsesAction
|
||||
{
|
||||
public function run(CustomForm $form, array $filters = []): array
|
||||
{
|
||||
$query = $form->responses()->with('form');
|
||||
|
||||
// Фильтр по статусу просмотра
|
||||
if (isset($filters['checked']) && $filters['checked'] !== '') {
|
||||
$query->where('checked', (bool) $filters['checked']);
|
||||
}
|
||||
|
||||
// Поиск по ID
|
||||
if (!empty($filters['search'])) {
|
||||
$query->where('id', 'like', '%' . $filters['search'] . '%');
|
||||
}
|
||||
|
||||
$responses = $query->orderByDesc('created_at')->paginate(20)->withQueryString();
|
||||
|
||||
// Динамические колонки на основе полей формы
|
||||
$columns = collect($form->columns ?? [])->map(function ($field) {
|
||||
return [
|
||||
'name' => $field['data']['name_field'] ?? '',
|
||||
'title' => $field['data']['title_field'] ?? '',
|
||||
'type' => $field['type'] ?? 'text',
|
||||
'options' => $this->extractOptions($field),
|
||||
];
|
||||
})->filter(fn($col) => !empty($col['name']))->values()->toArray();
|
||||
|
||||
return [
|
||||
'form' => $form,
|
||||
'responses' => $responses,
|
||||
'columns' => $columns,
|
||||
'filters' => $filters,
|
||||
];
|
||||
}
|
||||
|
||||
private function extractOptions(array $field): array
|
||||
{
|
||||
if (!in_array($field['type'], ['single_choice', 'multiple_choice'])) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return collect($field['data']['columns'] ?? [])
|
||||
->mapWithKeys(fn($opt) => [$opt['name_field'] ?? '' => $opt['title_field'] ?? ''])
|
||||
->toArray();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
<?php
|
||||
|
||||
namespace App\Containers\Dashboard\Actions\CustomForms;
|
||||
|
||||
use App\Containers\Widget\Models\CustomForm;
|
||||
|
||||
class UpdateCustomFormAction
|
||||
{
|
||||
public function run(CustomForm $form, array $data): CustomForm
|
||||
{
|
||||
$form->update($data);
|
||||
|
||||
return $form;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
namespace App\Containers\Dashboard\Actions\Departments;
|
||||
|
||||
use App\Containers\Education\Models\EducationalProgram;
|
||||
use App\Containers\InstituteStructure\Models\Department;
|
||||
use App\Services\App\Cache\DepartmentCacheService;
|
||||
|
||||
class AttachDepartmentProgramAction
|
||||
{
|
||||
public function __construct(
|
||||
private readonly DepartmentCacheService $cacheService,
|
||||
) {}
|
||||
|
||||
public function run(Department $department, EducationalProgram $program): void
|
||||
{
|
||||
$department->programs()->attach($program->id);
|
||||
|
||||
$this->cacheService->clearAllCacheByModel();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
namespace App\Containers\Dashboard\Actions\Departments;
|
||||
|
||||
use App\Containers\InstituteStructure\Models\Department;
|
||||
use App\Containers\User\Models\User;
|
||||
use App\Services\App\Cache\DepartmentCacheService;
|
||||
|
||||
class AttachDepartmentTeacherAction
|
||||
{
|
||||
public function __construct(
|
||||
private readonly DepartmentCacheService $cacheService,
|
||||
) {}
|
||||
|
||||
public function run(Department $department, User $user, array $data): void
|
||||
{
|
||||
$department->teachers()->attach($user->id, [
|
||||
'teaching_position' => $data['teaching_position'],
|
||||
'service_email' => $data['service_email'] ?? null,
|
||||
'service_phone' => $data['service_phone'] ?? null,
|
||||
'cabinet' => $data['cabinet'] ?? null,
|
||||
'sort' => $department->teachers()->count() + 1,
|
||||
]);
|
||||
|
||||
$this->cacheService->clearAllCacheByModel();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
namespace App\Containers\Dashboard\Actions\Departments;
|
||||
|
||||
use App\Containers\InstituteStructure\Models\Department;
|
||||
use App\Containers\User\Models\User;
|
||||
use App\Services\App\Cache\DepartmentCacheService;
|
||||
|
||||
class AttachDepartmentWorkerAction
|
||||
{
|
||||
public function __construct(
|
||||
private readonly DepartmentCacheService $cacheService,
|
||||
) {}
|
||||
|
||||
public function run(Department $department, User $user, array $data): void
|
||||
{
|
||||
$department->workers()->attach($user->id, [
|
||||
'position' => $data['position'],
|
||||
'service_email' => $data['service_email'] ?? null,
|
||||
'service_phone' => $data['service_phone'] ?? null,
|
||||
'cabinet' => $data['cabinet'] ?? null,
|
||||
'sort' => $department->workers()->count() + 1,
|
||||
]);
|
||||
|
||||
$this->cacheService->clearAllCacheByModel();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
<?php
|
||||
|
||||
namespace App\Containers\Dashboard\Actions\Departments;
|
||||
|
||||
use App\Containers\Dashboard\Tasks\Content\GenerateSearchDataTask;
|
||||
use App\Containers\InstituteStructure\Models\Department;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class CreateDepartmentAction
|
||||
{
|
||||
public function __construct(
|
||||
private readonly GenerateSearchDataTask $generateSearchDataTask,
|
||||
) {}
|
||||
|
||||
public function run(array $data): Department
|
||||
{
|
||||
// Генерируем slug если не передан
|
||||
if (empty($data['slug'])) {
|
||||
$data['slug'] = Str::slug($data['title']);
|
||||
}
|
||||
|
||||
// Генерируем search_data из контента
|
||||
if (!empty($data['content'])) {
|
||||
$data['search_data'] = $this->generateSearchDataTask->run($data['content']);
|
||||
}
|
||||
|
||||
$department = Department::create($data);
|
||||
|
||||
// Создаем SEO
|
||||
$this->generateSeo($department, $data);
|
||||
|
||||
return $department;
|
||||
}
|
||||
|
||||
private function generateSeo(Department $department, array $data): void
|
||||
{
|
||||
$title = $data['title'] ?? '';
|
||||
$description = null;
|
||||
|
||||
// Извлекаем description из первого paragraph блока
|
||||
if (!empty($data['content'])) {
|
||||
foreach ($data['content'] as $block) {
|
||||
if ($block['type'] === 'paragraph') {
|
||||
$description = strip_tags($block['data']['content']);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$department->seo()->create([
|
||||
'title' => $title,
|
||||
'description' => Str::limit(htmlspecialchars($description ?? '', ENT_QUOTES, 'UTF-8'), 160),
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
<?php
|
||||
|
||||
namespace App\Containers\Dashboard\Actions\Departments;
|
||||
|
||||
use App\Containers\InstituteStructure\Models\Department;
|
||||
|
||||
class DeleteDepartmentAction
|
||||
{
|
||||
public function run(Department $department): void
|
||||
{
|
||||
$department->delete();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
namespace App\Containers\Dashboard\Actions\Departments;
|
||||
|
||||
use App\Containers\Education\Models\EducationalProgram;
|
||||
use App\Containers\InstituteStructure\Models\Department;
|
||||
use App\Services\App\Cache\DepartmentCacheService;
|
||||
|
||||
class DetachDepartmentProgramAction
|
||||
{
|
||||
public function __construct(
|
||||
private readonly DepartmentCacheService $cacheService,
|
||||
) {}
|
||||
|
||||
public function run(Department $department, EducationalProgram $program): void
|
||||
{
|
||||
$department->programs()->detach($program->id);
|
||||
|
||||
$this->cacheService->clearAllCacheByModel();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
namespace App\Containers\Dashboard\Actions\Departments;
|
||||
|
||||
use App\Containers\InstituteStructure\Models\Department;
|
||||
use App\Containers\User\Models\User;
|
||||
use App\Services\App\Cache\DepartmentCacheService;
|
||||
|
||||
class DetachDepartmentTeacherAction
|
||||
{
|
||||
public function __construct(
|
||||
private readonly DepartmentCacheService $cacheService,
|
||||
) {}
|
||||
|
||||
public function run(Department $department, User $user): void
|
||||
{
|
||||
$department->teachers()->detach($user->id);
|
||||
|
||||
$this->cacheService->clearAllCacheByModel();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
namespace App\Containers\Dashboard\Actions\Departments;
|
||||
|
||||
use App\Containers\InstituteStructure\Models\Department;
|
||||
use App\Containers\User\Models\User;
|
||||
use App\Services\App\Cache\DepartmentCacheService;
|
||||
|
||||
class DetachDepartmentWorkerAction
|
||||
{
|
||||
public function __construct(
|
||||
private readonly DepartmentCacheService $cacheService,
|
||||
) {}
|
||||
|
||||
public function run(Department $department, User $user): void
|
||||
{
|
||||
$department->workers()->detach($user->id);
|
||||
|
||||
$this->cacheService->clearAllCacheByModel();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
namespace App\Containers\Dashboard\Actions\Departments;
|
||||
|
||||
use App\Containers\InstituteStructure\Models\Department;
|
||||
use App\Services\App\Cache\DepartmentCacheService;
|
||||
|
||||
class ListDepartmentProgramsAction
|
||||
{
|
||||
public function run(Department $department, array $filters = []): array
|
||||
{
|
||||
$query = $department->programs();
|
||||
|
||||
// Фильтр по статусу
|
||||
if (!empty($filters['status'])) {
|
||||
$query->where('status', $filters['status']);
|
||||
}
|
||||
|
||||
// Поиск по названию
|
||||
if (!empty($filters['search'])) {
|
||||
$query->where('name', 'like', '%' . $filters['search'] . '%');
|
||||
}
|
||||
|
||||
$programs = $query->orderBy('name')->paginate(20)->withQueryString();
|
||||
|
||||
return [
|
||||
'programs' => $programs,
|
||||
'filters' => $filters,
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
namespace App\Containers\Dashboard\Actions\Departments;
|
||||
|
||||
use App\Containers\InstituteStructure\Models\Department;
|
||||
|
||||
class ListDepartmentTeachersAction
|
||||
{
|
||||
public function run(Department $department, array $filters = []): array
|
||||
{
|
||||
$query = $department->teachers();
|
||||
|
||||
// Поиск по имени
|
||||
if (!empty($filters['search'])) {
|
||||
$query->where('name', 'like', '%' . $filters['search'] . '%');
|
||||
}
|
||||
|
||||
// Фильтр по должности
|
||||
if (!empty($filters['position'])) {
|
||||
$query->where('teaching_position', 'like', '%' . $filters['position'] . '%');
|
||||
}
|
||||
|
||||
$teachers = $query->orderBy('teachers_departments.sort')->paginate(20)->withQueryString();
|
||||
|
||||
return [
|
||||
'teachers' => $teachers,
|
||||
'filters' => $filters,
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
namespace App\Containers\Dashboard\Actions\Departments;
|
||||
|
||||
use App\Containers\InstituteStructure\Models\Department;
|
||||
use App\Containers\User\Models\User;
|
||||
|
||||
class ListDepartmentWorkersAction
|
||||
{
|
||||
public function run(Department $department, array $filters = []): array
|
||||
{
|
||||
$query = $department->workers();
|
||||
|
||||
// Поиск по имени
|
||||
if (!empty($filters['search'])) {
|
||||
$query->where('name', 'like', '%' . $filters['search'] . '%');
|
||||
}
|
||||
|
||||
// Фильтр по должности
|
||||
if (!empty($filters['position'])) {
|
||||
$query->where('position', 'like', '%' . $filters['position'] . '%');
|
||||
}
|
||||
|
||||
$workers = $query->orderBy('workers_departments.sort')->paginate(20)->withQueryString();
|
||||
|
||||
return [
|
||||
'workers' => $workers,
|
||||
'filters' => $filters,
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
namespace App\Containers\Dashboard\Actions\Departments;
|
||||
|
||||
use App\Containers\InstituteStructure\Models\Department;
|
||||
|
||||
class ListDepartmentsAction
|
||||
{
|
||||
public function run(array $filters = []): array
|
||||
{
|
||||
$query = Department::query()->with(['faculty']);
|
||||
|
||||
// Фильтр по факультету
|
||||
if (isset($filters['faculty_id']) && $filters['faculty_id'] !== '') {
|
||||
$query->where('faculty_id', (int) $filters['faculty_id']);
|
||||
}
|
||||
|
||||
// Фильтр по статусу
|
||||
if (isset($filters['is_active']) && $filters['is_active'] !== '') {
|
||||
$query->where('is_active', (bool) $filters['is_active']);
|
||||
}
|
||||
|
||||
// Поиск по названию
|
||||
if (!empty($filters['search'])) {
|
||||
$query->where('title', 'like', '%' . $filters['search'] . '%');
|
||||
}
|
||||
|
||||
$departments = $query->orderBy('title')->paginate(20)->withQueryString();
|
||||
|
||||
return [
|
||||
'departments' => $departments,
|
||||
'filters' => $filters,
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
<?php
|
||||
|
||||
namespace App\Containers\Dashboard\Actions\Departments;
|
||||
|
||||
use App\Containers\Dashboard\Tasks\Content\GenerateSearchDataTask;
|
||||
use App\Containers\InstituteStructure\Models\Department;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class UpdateDepartmentAction
|
||||
{
|
||||
public function __construct(
|
||||
private readonly GenerateSearchDataTask $generateSearchDataTask,
|
||||
) {}
|
||||
|
||||
public function run(Department $department, array $data): Department
|
||||
{
|
||||
// Генерируем search_data из контента только если контент передан и не пуст
|
||||
if (isset($data['content']) && !empty($data['content'])) {
|
||||
$data['search_data'] = $this->generateSearchDataTask->run($data['content']);
|
||||
}
|
||||
|
||||
$department->update($data);
|
||||
|
||||
// Обновляем SEO
|
||||
$this->updateSeo($department, $data);
|
||||
|
||||
return $department;
|
||||
}
|
||||
|
||||
private function updateSeo(Department $department, array $data): void
|
||||
{
|
||||
$title = $data['title'] ?? $department->title;
|
||||
$description = null;
|
||||
|
||||
// Извлекаем description из первого paragraph блока
|
||||
if (!empty($data['content'])) {
|
||||
foreach ($data['content'] as $block) {
|
||||
if ($block['type'] === 'paragraph') {
|
||||
$description = strip_tags($block['data']['content']);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$seoData = [
|
||||
'title' => $title,
|
||||
'description' => Str::limit(htmlspecialchars($description ?? '', ENT_QUOTES, 'UTF-8'), 160),
|
||||
];
|
||||
|
||||
if ($department->seo) {
|
||||
$department->seo->update($seoData);
|
||||
} else {
|
||||
$department->seo()->create($seoData);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
namespace App\Containers\Dashboard\Actions\Departments;
|
||||
|
||||
use App\Containers\InstituteStructure\Models\Department;
|
||||
use App\Containers\User\Models\User;
|
||||
use App\Services\App\Cache\DepartmentCacheService;
|
||||
|
||||
class UpdateDepartmentTeacherAction
|
||||
{
|
||||
public function __construct(
|
||||
private readonly DepartmentCacheService $cacheService,
|
||||
) {}
|
||||
|
||||
public function run(Department $department, User $user, array $data): void
|
||||
{
|
||||
$department->teachers()->updateExistingPivot($user->id, [
|
||||
'teaching_position' => $data['teaching_position'],
|
||||
'service_email' => $data['service_email'] ?? null,
|
||||
'service_phone' => $data['service_phone'] ?? null,
|
||||
'cabinet' => $data['cabinet'] ?? null,
|
||||
]);
|
||||
|
||||
$this->cacheService->clearAllCacheByModel();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
namespace App\Containers\Dashboard\Actions\Departments;
|
||||
|
||||
use App\Containers\InstituteStructure\Models\Department;
|
||||
use App\Containers\User\Models\User;
|
||||
use App\Services\App\Cache\DepartmentCacheService;
|
||||
|
||||
class UpdateDepartmentWorkerAction
|
||||
{
|
||||
public function __construct(
|
||||
private readonly DepartmentCacheService $cacheService,
|
||||
) {}
|
||||
|
||||
public function run(Department $department, User $user, array $data): void
|
||||
{
|
||||
$department->workers()->updateExistingPivot($user->id, [
|
||||
'position' => $data['position'],
|
||||
'service_email' => $data['service_email'] ?? null,
|
||||
'service_phone' => $data['service_phone'] ?? null,
|
||||
'cabinet' => $data['cabinet'] ?? null,
|
||||
]);
|
||||
|
||||
$this->cacheService->clearAllCacheByModel();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
<?php
|
||||
|
||||
namespace App\Containers\Dashboard\Actions\DirectionStudies;
|
||||
|
||||
use App\Containers\Education\Models\DirectionStudy;
|
||||
|
||||
class CreateDirectionStudyAction
|
||||
{
|
||||
public function run(array $data): DirectionStudy
|
||||
{
|
||||
return DirectionStudy::create($data);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
<?php
|
||||
|
||||
namespace App\Containers\Dashboard\Actions\DirectionStudies;
|
||||
|
||||
use App\Containers\Education\Models\DirectionStudy;
|
||||
|
||||
class DeleteDirectionStudyAction
|
||||
{
|
||||
public function run(DirectionStudy $direction): bool
|
||||
{
|
||||
return $direction->delete();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
namespace App\Containers\Dashboard\Actions\DirectionStudies;
|
||||
|
||||
use App\Containers\Education\Models\DirectionStudy;
|
||||
use App\Ship\Enums\Education\LevelEducational;
|
||||
|
||||
class ListDirectionStudiesAction
|
||||
{
|
||||
public function run(array $filters = []): array
|
||||
{
|
||||
$query = DirectionStudy::query();
|
||||
|
||||
// Фильтр по уровню образования
|
||||
if (!empty($filters['lvl_edu'])) {
|
||||
$query->where('lvl_edu', $filters['lvl_edu']);
|
||||
}
|
||||
|
||||
// Поиск по коду или названию
|
||||
if (!empty($filters['search'])) {
|
||||
$search = $filters['search'];
|
||||
$query->where(function ($q) use ($search) {
|
||||
$q->where('code', 'like', '%' . $search . '%')
|
||||
->orWhere('name', 'like', '%' . $search . '%');
|
||||
});
|
||||
}
|
||||
|
||||
$directions = $query->orderBy('code')->paginate(20)->withQueryString();
|
||||
|
||||
return [
|
||||
'directions' => $directions,
|
||||
'filters' => $filters,
|
||||
'educationLevels' => array_map(fn($level) => [
|
||||
'value' => $level->value,
|
||||
'label' => $level->getLabel(),
|
||||
'color' => $level->getColor(),
|
||||
], LevelEducational::cases()),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
|
||||
namespace App\Containers\Dashboard\Actions\DirectionStudies;
|
||||
|
||||
use App\Containers\Education\Models\DirectionStudy;
|
||||
|
||||
class UpdateDirectionStudyAction
|
||||
{
|
||||
public function run(DirectionStudy $direction, array $data): DirectionStudy
|
||||
{
|
||||
$direction->update($data);
|
||||
return $direction;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
namespace App\Containers\Dashboard\Actions\Divisions;
|
||||
|
||||
use App\Containers\InstituteStructure\Models\Division;
|
||||
use App\Containers\User\Models\User;
|
||||
use App\Services\App\Cache\DivisionCacheService;
|
||||
|
||||
class AttachDivisionWorkerAction
|
||||
{
|
||||
public function __construct(
|
||||
private readonly DivisionCacheService $cacheService,
|
||||
) {}
|
||||
|
||||
public function run(Division $division, User $user, array $data): void
|
||||
{
|
||||
$division->workers()->attach($user->id, [
|
||||
'administrativePosition' => $data['administrativePosition'],
|
||||
'service_email' => $data['service_email'] ?? null,
|
||||
'service_phone' => $data['service_phone'] ?? null,
|
||||
'cabinet' => $data['cabinet'] ?? null,
|
||||
'sort' => $data['sort'] ?? $division->workers()->count(),
|
||||
]);
|
||||
|
||||
$this->cacheService->clearAllCacheByModel();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
<?php
|
||||
|
||||
namespace App\Containers\Dashboard\Actions\Divisions;
|
||||
|
||||
use App\Containers\Dashboard\Tasks\Content\GenerateSearchDataTask;
|
||||
use App\Containers\InstituteStructure\Models\Division;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class CreateDivisionAction
|
||||
{
|
||||
public function __construct(
|
||||
private readonly GenerateSearchDataTask $generateSearchDataTask,
|
||||
) {}
|
||||
|
||||
public function run(array $data): Division
|
||||
{
|
||||
// Генерируем slug если не передан
|
||||
if (empty($data['slug'])) {
|
||||
$data['slug'] = Str::slug($data['title']);
|
||||
}
|
||||
|
||||
// Генерируем search_data из описания
|
||||
if (!empty($data['description'])) {
|
||||
$data['search_data'] = $this->generateSearchDataTask->run($data['description']);
|
||||
}
|
||||
|
||||
$division = Division::create($data);
|
||||
|
||||
// Создаем SEO
|
||||
$this->generateSeo($division, $data);
|
||||
|
||||
return $division;
|
||||
}
|
||||
|
||||
private function generateSeo(Division $division, array $data): void
|
||||
{
|
||||
$title = $data['title'] ?? '';
|
||||
$description = null;
|
||||
|
||||
// Извлекаем description из первого paragraph блока
|
||||
if (!empty($data['description'])) {
|
||||
foreach ($data['description'] as $block) {
|
||||
if ($block['type'] === 'paragraph') {
|
||||
$description = strip_tags($block['data']['content']);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$division->seo()->create([
|
||||
'title' => $title,
|
||||
'description' => Str::limit(htmlspecialchars($description ?? '', ENT_QUOTES, 'UTF-8'), 160),
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
<?php
|
||||
|
||||
namespace App\Containers\Dashboard\Actions\Divisions;
|
||||
|
||||
use App\Containers\InstituteStructure\Models\Division;
|
||||
|
||||
class DeleteDivisionAction
|
||||
{
|
||||
public function run(Division $division): void
|
||||
{
|
||||
$division->delete();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
namespace App\Containers\Dashboard\Actions\Divisions;
|
||||
|
||||
use App\Containers\InstituteStructure\Models\Division;
|
||||
use App\Containers\User\Models\User;
|
||||
use App\Services\App\Cache\DivisionCacheService;
|
||||
|
||||
class DetachDivisionWorkerAction
|
||||
{
|
||||
public function __construct(
|
||||
private readonly DivisionCacheService $cacheService,
|
||||
) {}
|
||||
|
||||
public function run(Division $division, User $worker): void
|
||||
{
|
||||
$division->workers()->detach($worker->id);
|
||||
$this->cacheService->clearAllCacheByModel();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
namespace App\Containers\Dashboard\Actions\Divisions;
|
||||
|
||||
use App\Containers\InstituteStructure\Models\Division;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
|
||||
class ListDivisionWorkersAction
|
||||
{
|
||||
public function run(Division $division, array $filters = []): array
|
||||
{
|
||||
$query = $division->workers()
|
||||
->withPivot(['administrativePosition', 'sort', 'service_email', 'service_phone', 'cabinet'])
|
||||
->whereHas('userDetail');
|
||||
|
||||
// Поиск по ФИО
|
||||
if (!empty($filters['search'])) {
|
||||
$query->where('name', 'like', '%' . $filters['search'] . '%');
|
||||
}
|
||||
|
||||
// Фильтр по должности
|
||||
if (!empty($filters['position'])) {
|
||||
$query->where('division_user.administrativePosition', 'like', '%' . $filters['position'] . '%');
|
||||
}
|
||||
|
||||
$workers = $query->orderBy('division_user.sort')->paginate(20)->withQueryString();
|
||||
|
||||
return [
|
||||
'workers' => $workers,
|
||||
'filters' => $filters,
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
namespace App\Containers\Dashboard\Actions\Divisions;
|
||||
|
||||
use App\Containers\InstituteStructure\Models\Division;
|
||||
|
||||
class ListDivisionsAction
|
||||
{
|
||||
public function run(array $filters = []): array
|
||||
{
|
||||
$query = Division::query();
|
||||
|
||||
// Фильтр по статусу (по умолчанию только активные)
|
||||
if (isset($filters['is_active']) && $filters['is_active'] !== '') {
|
||||
$query->where('is_active', (bool) $filters['is_active']);
|
||||
} elseif (!isset($filters['is_active'])) {
|
||||
$query->where('is_active', true);
|
||||
}
|
||||
|
||||
// Поиск по названию
|
||||
if (!empty($filters['search'])) {
|
||||
$query->where('title', 'like', '%' . $filters['search'] . '%')
|
||||
->orWhere('slug', 'like', '%' . $filters['search'] . '%');
|
||||
}
|
||||
|
||||
$divisions = $query->orderBy('created_at', 'desc')->paginate(20)->withQueryString();
|
||||
|
||||
return [
|
||||
'divisions' => $divisions,
|
||||
'filters' => $filters,
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
<?php
|
||||
|
||||
namespace App\Containers\Dashboard\Actions\Divisions;
|
||||
|
||||
use App\Containers\Dashboard\Tasks\Content\GenerateSearchDataTask;
|
||||
use App\Containers\InstituteStructure\Models\Division;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class UpdateDivisionAction
|
||||
{
|
||||
public function __construct(
|
||||
private readonly GenerateSearchDataTask $generateSearchDataTask,
|
||||
) {}
|
||||
|
||||
public function run(Division $division, array $data): Division
|
||||
{
|
||||
// Генерируем search_data из описания только если описание передано и не пусто
|
||||
if (isset($data['description']) && !empty($data['description'])) {
|
||||
$data['search_data'] = $this->generateSearchDataTask->run($data['description']);
|
||||
}
|
||||
|
||||
$division->update($data);
|
||||
|
||||
// Обновляем SEO
|
||||
$this->updateSeo($division, $data);
|
||||
|
||||
return $division;
|
||||
}
|
||||
|
||||
private function updateSeo(Division $division, array $data): void
|
||||
{
|
||||
$title = $data['title'] ?? $division->title;
|
||||
$description = null;
|
||||
|
||||
// Извлекаем description из первого paragraph блока
|
||||
if (!empty($data['description'])) {
|
||||
foreach ($data['description'] as $block) {
|
||||
if ($block['type'] === 'paragraph') {
|
||||
$description = strip_tags($block['data']['content']);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$seoData = [
|
||||
'title' => $title,
|
||||
'description' => Str::limit(htmlspecialchars($description ?? '', ENT_QUOTES, 'UTF-8'), 160),
|
||||
];
|
||||
|
||||
if ($division->seo) {
|
||||
$division->seo->update($seoData);
|
||||
} else {
|
||||
$division->seo()->create($seoData);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
namespace App\Containers\Dashboard\Actions\Divisions;
|
||||
|
||||
use App\Containers\InstituteStructure\Models\Division;
|
||||
use App\Containers\User\Models\User;
|
||||
use App\Services\App\Cache\DivisionCacheService;
|
||||
|
||||
class UpdateDivisionWorkerAction
|
||||
{
|
||||
public function __construct(
|
||||
private readonly DivisionCacheService $cacheService,
|
||||
) {}
|
||||
|
||||
public function run(Division $division, User $worker, array $data): void
|
||||
{
|
||||
$division->workers()->updateExistingPivot($worker->id, [
|
||||
'administrativePosition' => $data['administrativePosition'],
|
||||
'service_email' => $data['service_email'] ?? null,
|
||||
'service_phone' => $data['service_phone'] ?? null,
|
||||
'cabinet' => $data['cabinet'] ?? null,
|
||||
]);
|
||||
|
||||
$this->cacheService->clearAllCacheByModel();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
<?php
|
||||
|
||||
namespace App\Containers\Dashboard\Actions\EducationalGroups;
|
||||
|
||||
use App\Containers\Schedule\Models\EducationalGroup;
|
||||
|
||||
class CreateEducationalGroupAction
|
||||
{
|
||||
public function run(array $data): EducationalGroup
|
||||
{
|
||||
return EducationalGroup::create($data);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
<?php
|
||||
|
||||
namespace App\Containers\Dashboard\Actions\EducationalGroups;
|
||||
|
||||
use App\Containers\Schedule\Models\EducationalGroup;
|
||||
|
||||
class DeleteEducationalGroupAction
|
||||
{
|
||||
public function run(EducationalGroup $group): bool
|
||||
{
|
||||
return $group->delete();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
<?php
|
||||
|
||||
namespace App\Containers\Dashboard\Actions\EducationalGroups;
|
||||
|
||||
use App\Containers\Schedule\Models\EducationalGroup;
|
||||
use App\Ship\Enums\Education\FormEducation;
|
||||
use App\Containers\InstituteStructure\Models\Faculty;
|
||||
|
||||
class ListEducationalGroupsAction
|
||||
{
|
||||
public function run(array $filters = []): array
|
||||
{
|
||||
$query = EducationalGroup::with(['faculty']);
|
||||
|
||||
// Фильтр по факультету
|
||||
if (!empty($filters['faculty_id'])) {
|
||||
$query->where('faculty_id', $filters['faculty_id']);
|
||||
}
|
||||
|
||||
// Фильтр по форме обучения
|
||||
if (!empty($filters['education_form_id'])) {
|
||||
$query->where('education_form_id', $filters['education_form_id']);
|
||||
}
|
||||
|
||||
// Поиск по названию группы
|
||||
if (!empty($filters['search'])) {
|
||||
$query->where('title', 'like', '%' . $filters['search'] . '%');
|
||||
}
|
||||
|
||||
$groups = $query->orderBy('title')->paginate(20)->withQueryString();
|
||||
|
||||
return [
|
||||
'groups' => $groups,
|
||||
'filters' => $filters,
|
||||
'faculties' => Faculty::orderBy('title')->get(['id', 'title']),
|
||||
'educationForms' => array_map(fn($form) => [
|
||||
'value' => $form->value,
|
||||
'label' => $form->getLabel(),
|
||||
'color' => $form->getColor(),
|
||||
'name' => $form->name,
|
||||
], FormEducation::cases()),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
|
||||
namespace App\Containers\Dashboard\Actions\EducationalGroups;
|
||||
|
||||
use App\Containers\Schedule\Models\EducationalGroup;
|
||||
|
||||
class UpdateEducationalGroupAction
|
||||
{
|
||||
public function run(EducationalGroup $group, array $data): EducationalGroup
|
||||
{
|
||||
$group->update($data);
|
||||
return $group->fresh();
|
||||
}
|
||||
}
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
<?php
|
||||
|
||||
namespace App\Containers\Dashboard\Actions\EducationalPrograms;
|
||||
|
||||
use App\Containers\Education\Models\EducationalProgram;
|
||||
|
||||
class CreateEducationalProgramAction
|
||||
{
|
||||
public function run(array $data): EducationalProgram
|
||||
{
|
||||
return EducationalProgram::create($data);
|
||||
}
|
||||
}
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
<?php
|
||||
|
||||
namespace App\Containers\Dashboard\Actions\EducationalPrograms;
|
||||
|
||||
use App\Containers\Education\Models\EducationalProgram;
|
||||
|
||||
class DeleteEducationalProgramAction
|
||||
{
|
||||
public function run(EducationalProgram $program): bool
|
||||
{
|
||||
return $program->delete();
|
||||
}
|
||||
}
|
||||
+55
@@ -0,0 +1,55 @@
|
||||
<?php
|
||||
|
||||
namespace App\Containers\Dashboard\Actions\EducationalPrograms;
|
||||
|
||||
use App\Containers\Education\Models\EducationalProgram;
|
||||
use App\Containers\Education\Models\DirectionStudy;
|
||||
use App\Ship\Enums\Education\EducationalProgramStatus;
|
||||
use App\Ship\Enums\Education\LevelEducational;
|
||||
|
||||
class ListEducationalProgramsAction
|
||||
{
|
||||
public function run(array $filters = []): array
|
||||
{
|
||||
$query = EducationalProgram::with(['directionStudy']);
|
||||
|
||||
// Фильтр по уровню образования
|
||||
if (!empty($filters['lvl_edu'])) {
|
||||
$query->where('lvl_edu', $filters['lvl_edu']);
|
||||
}
|
||||
|
||||
// Фильтр по статусу
|
||||
if (!empty($filters['status'])) {
|
||||
$query->where('status', $filters['status']);
|
||||
}
|
||||
|
||||
// Фильтр по направлению подготовки
|
||||
if (!empty($filters['direction_study_id'])) {
|
||||
$query->where('direction_study_id', $filters['direction_study_id']);
|
||||
}
|
||||
|
||||
// Поиск по названию
|
||||
if (!empty($filters['search'])) {
|
||||
$search = $filters['search'];
|
||||
$query->where('name', 'like', '%' . $search . '%');
|
||||
}
|
||||
|
||||
$programs = $query->orderBy('name')->paginate(20)->withQueryString();
|
||||
|
||||
return [
|
||||
'programs' => $programs,
|
||||
'filters' => $filters,
|
||||
'statuses' => array_map(fn($status) => [
|
||||
'value' => $status->value,
|
||||
'label' => $status->getLabel(),
|
||||
'color' => $status->getColor(),
|
||||
], EducationalProgramStatus::cases()),
|
||||
'educationLevels' => array_map(fn($level) => [
|
||||
'value' => $level->value,
|
||||
'label' => $level->getLabel(),
|
||||
'color' => $level->getColor(),
|
||||
], LevelEducational::cases()),
|
||||
'directionStudies' => DirectionStudy::orderBy('code')->get(['id', 'code', 'name']),
|
||||
];
|
||||
}
|
||||
}
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
|
||||
namespace App\Containers\Dashboard\Actions\EducationalPrograms;
|
||||
|
||||
use App\Containers\Education\Models\EducationalProgram;
|
||||
|
||||
class UpdateEducationalProgramAction
|
||||
{
|
||||
public function run(EducationalProgram $program, array $data): EducationalProgram
|
||||
{
|
||||
$program->update($data);
|
||||
return $program;
|
||||
}
|
||||
}
|
||||
@@ -189,6 +189,9 @@ class FetchEmailNewsAction
|
||||
'subject' => $email['subject'] ?? 'unknown',
|
||||
]);
|
||||
|
||||
// Помечаем письмо как прочитанное чтобы не обрабатывать повторно
|
||||
$this->markEmail($email['message'], $folder);
|
||||
|
||||
return [
|
||||
'success' => false,
|
||||
'error' => 'Нет DOC/DOCX файла для извлечения текста',
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
namespace App\Containers\Dashboard\Actions\Faculties;
|
||||
|
||||
use App\Containers\InstituteStructure\Models\Faculty;
|
||||
use App\Containers\User\Models\User;
|
||||
use App\Services\App\Cache\FacultyCacheService;
|
||||
|
||||
class AttachFacultyWorkerAction
|
||||
{
|
||||
public function __construct(
|
||||
private readonly FacultyCacheService $cacheService,
|
||||
) {}
|
||||
|
||||
public function run(Faculty $faculty, User $user, array $data): void
|
||||
{
|
||||
$faculty->workers()->attach($user->id, [
|
||||
'position' => $data['position'],
|
||||
'service_email' => $data['service_email'] ?? null,
|
||||
'service_phone' => $data['service_phone'] ?? null,
|
||||
'cabinet' => $data['cabinet'] ?? null,
|
||||
'sort' => $data['sort'] ?? $faculty->workers()->count(),
|
||||
]);
|
||||
|
||||
$this->cacheService->clearAllCacheByModel();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
<?php
|
||||
|
||||
namespace App\Containers\Dashboard\Actions\Faculties;
|
||||
|
||||
use App\Containers\Dashboard\Tasks\Content\GenerateSearchDataTask;
|
||||
use App\Containers\InstituteStructure\Models\Faculty;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class CreateFacultyAction
|
||||
{
|
||||
public function __construct(
|
||||
private readonly GenerateSearchDataTask $generateSearchDataTask,
|
||||
) {}
|
||||
|
||||
public function run(array $data): Faculty
|
||||
{
|
||||
// Генерируем slug если не передан
|
||||
if (empty($data['slug'])) {
|
||||
$data['slug'] = Str::slug($data['title']);
|
||||
}
|
||||
|
||||
// Генерируем search_data из контента
|
||||
if (!empty($data['content'])) {
|
||||
$data['search_data'] = $this->generateSearchDataTask->run($data['content']);
|
||||
}
|
||||
|
||||
$faculty = Faculty::create($data);
|
||||
|
||||
// Создаем SEO
|
||||
$this->generateSeo($faculty, $data);
|
||||
|
||||
return $faculty;
|
||||
}
|
||||
|
||||
private function generateSeo(Faculty $faculty, array $data): void
|
||||
{
|
||||
$title = $data['title'] ?? '';
|
||||
$description = null;
|
||||
|
||||
// Извлекаем description из первого paragraph блока
|
||||
if (!empty($data['content'])) {
|
||||
foreach ($data['content'] as $block) {
|
||||
if ($block['type'] === 'paragraph') {
|
||||
$description = strip_tags($block['data']['content']);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$faculty->seo()->create([
|
||||
'title' => $title,
|
||||
'description' => Str::limit(htmlspecialchars($description ?? '', ENT_QUOTES, 'UTF-8'), 160),
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
<?php
|
||||
|
||||
namespace App\Containers\Dashboard\Actions\Faculties;
|
||||
|
||||
use App\Containers\InstituteStructure\Models\Faculty;
|
||||
|
||||
class DeleteFacultyAction
|
||||
{
|
||||
public function run(Faculty $faculty): void
|
||||
{
|
||||
$faculty->delete();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
namespace App\Containers\Dashboard\Actions\Faculties;
|
||||
|
||||
use App\Containers\InstituteStructure\Models\Faculty;
|
||||
use App\Containers\User\Models\User;
|
||||
use App\Services\App\Cache\FacultyCacheService;
|
||||
|
||||
class DetachFacultyWorkerAction
|
||||
{
|
||||
public function __construct(
|
||||
private readonly FacultyCacheService $cacheService,
|
||||
) {}
|
||||
|
||||
public function run(Faculty $faculty, User $worker): void
|
||||
{
|
||||
$faculty->workers()->detach($worker->id);
|
||||
$this->cacheService->clearAllCacheByModel();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
namespace App\Containers\Dashboard\Actions\Faculties;
|
||||
|
||||
use App\Containers\InstituteStructure\Models\Faculty;
|
||||
|
||||
class ListFacultiesAction
|
||||
{
|
||||
public function run(array $filters = []): array
|
||||
{
|
||||
$query = Faculty::query();
|
||||
|
||||
// Фильтр по статусу
|
||||
if (isset($filters['is_active']) && $filters['is_active'] !== '') {
|
||||
$query->where('is_active', (bool) $filters['is_active']);
|
||||
}
|
||||
|
||||
// Поиск по названию
|
||||
if (!empty($filters['search'])) {
|
||||
$query->where('title', 'like', '%' . $filters['search'] . '%');
|
||||
}
|
||||
|
||||
$faculties = $query->orderBy('title')->paginate(20)->withQueryString();
|
||||
|
||||
return [
|
||||
'faculties' => $faculties,
|
||||
'filters' => $filters,
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
namespace App\Containers\Dashboard\Actions\Faculties;
|
||||
|
||||
use App\Containers\InstituteStructure\Models\Faculty;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
|
||||
class ListFacultyWorkersAction
|
||||
{
|
||||
public function run(Faculty $faculty, array $filters = []): array
|
||||
{
|
||||
$query = $faculty->workers()
|
||||
->withPivot(['position', 'sort', 'service_email', 'service_phone', 'cabinet'])
|
||||
->whereHas('userDetail');
|
||||
|
||||
// Поиск по ФИО
|
||||
if (!empty($filters['search'])) {
|
||||
$query->where('name', 'like', '%' . $filters['search'] . '%');
|
||||
}
|
||||
|
||||
// Фильтр по должности
|
||||
if (!empty($filters['position'])) {
|
||||
$query->where('workers_faculties.position', 'like', '%' . $filters['position'] . '%');
|
||||
}
|
||||
|
||||
$workers = $query->orderBy('workers_faculties.sort')->paginate(20)->withQueryString();
|
||||
|
||||
return [
|
||||
'workers' => $workers,
|
||||
'filters' => $filters,
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
<?php
|
||||
|
||||
namespace App\Containers\Dashboard\Actions\Faculties;
|
||||
|
||||
use App\Containers\Dashboard\Tasks\Content\GenerateSearchDataTask;
|
||||
use App\Containers\InstituteStructure\Models\Faculty;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class UpdateFacultyAction
|
||||
{
|
||||
public function __construct(
|
||||
private readonly GenerateSearchDataTask $generateSearchDataTask,
|
||||
) {}
|
||||
|
||||
public function run(Faculty $faculty, array $data): Faculty
|
||||
{
|
||||
// Генерируем search_data из контента только если контент передан и не пуст
|
||||
if (isset($data['content']) && !empty($data['content'])) {
|
||||
$data['search_data'] = $this->generateSearchDataTask->run($data['content']);
|
||||
}
|
||||
|
||||
$faculty->update($data);
|
||||
|
||||
// Обновляем SEO
|
||||
$this->updateSeo($faculty, $data);
|
||||
|
||||
return $faculty;
|
||||
}
|
||||
|
||||
private function updateSeo(Faculty $faculty, array $data): void
|
||||
{
|
||||
$title = $data['title'] ?? $faculty->title;
|
||||
$description = null;
|
||||
|
||||
// Извлекаем description из первого paragraph блока
|
||||
if (!empty($data['content'])) {
|
||||
foreach ($data['content'] as $block) {
|
||||
if ($block['type'] === 'paragraph') {
|
||||
$description = strip_tags($block['data']['content']);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$seoData = [
|
||||
'title' => $title,
|
||||
'description' => Str::limit(htmlspecialchars($description ?? '', ENT_QUOTES, 'UTF-8'), 160),
|
||||
];
|
||||
|
||||
if ($faculty->seo) {
|
||||
$faculty->seo->update($seoData);
|
||||
} else {
|
||||
$faculty->seo()->create($seoData);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
namespace App\Containers\Dashboard\Actions\Faculties;
|
||||
|
||||
use App\Containers\InstituteStructure\Models\Faculty;
|
||||
use App\Containers\User\Models\User;
|
||||
use App\Services\App\Cache\FacultyCacheService;
|
||||
|
||||
class UpdateFacultyWorkerAction
|
||||
{
|
||||
public function __construct(
|
||||
private readonly FacultyCacheService $cacheService,
|
||||
) {}
|
||||
|
||||
public function run(Faculty $faculty, User $worker, array $data): void
|
||||
{
|
||||
$faculty->workers()->updateExistingPivot($worker->id, [
|
||||
'position' => $data['position'],
|
||||
'service_email' => $data['service_email'] ?? null,
|
||||
'service_phone' => $data['service_phone'] ?? null,
|
||||
'cabinet' => $data['cabinet'] ?? null,
|
||||
]);
|
||||
|
||||
$this->cacheService->clearAllCacheByModel();
|
||||
}
|
||||
}
|
||||
@@ -1,234 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Containers\Dashboard\Actions;
|
||||
|
||||
use App\Containers\Dashboard\Data\EmailAttachmentData;
|
||||
use App\Containers\Dashboard\Exceptions\EmailFetchException;
|
||||
use App\Containers\Dashboard\Tasks\ConnectToImapTask;
|
||||
use App\Containers\Dashboard\Tasks\DownloadAttachmentsTask;
|
||||
use App\Containers\Dashboard\Tasks\FetchUnreadEmailsTask;
|
||||
use App\Containers\Dashboard\Tasks\FilterBySenderTask;
|
||||
use App\Containers\Dashboard\Tasks\MarkEmailAsReadTask;
|
||||
use App\Containers\Dashboard\Actions\ProcessMixedFilesAction;
|
||||
use Illuminate\Http\UploadedFile;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Webklex\PHPIMAP\Client;
|
||||
use Webklex\PHPIMAP\Folder;
|
||||
|
||||
/**
|
||||
* Оркестрация процесса получения новостей из Email
|
||||
*/
|
||||
class FetchEmailNewsAction
|
||||
{
|
||||
public function __construct(
|
||||
private readonly ConnectToImapTask $connectToImapTask,
|
||||
private readonly FetchUnreadEmailsTask $fetchUnreadEmailsTask,
|
||||
private readonly FilterBySenderTask $filterBySenderTask,
|
||||
private readonly DownloadAttachmentsTask $downloadAttachmentsTask,
|
||||
private readonly MarkEmailAsReadTask $markEmailAsReadTask,
|
||||
private readonly ProcessMixedFilesAction $processMixedFilesAction,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Выполнить получение и обработку новостей из Email
|
||||
*
|
||||
* @return array Результат обработки
|
||||
* @throws EmailFetchException
|
||||
*/
|
||||
public function run(): array
|
||||
{
|
||||
// Проверяем, включена ли функция
|
||||
if (!config('email-news.enabled', true)) {
|
||||
Log::warning('[FetchEmailNewsAction] Функция отключена в конфиге');
|
||||
throw EmailFetchException::featureDisabled();
|
||||
}
|
||||
|
||||
Log::info('[FetchEmailNewsAction] Начало получения новостей из Email');
|
||||
|
||||
$result = [
|
||||
'processed_emails' => 0,
|
||||
'skipped_emails' => 0,
|
||||
'created_posts' => 0,
|
||||
'errors' => [],
|
||||
'posts' => [],
|
||||
];
|
||||
|
||||
try {
|
||||
// Подключаемся к IMAP
|
||||
$client = $this->connectToImapTask->run();
|
||||
|
||||
// Получаем папку
|
||||
$folder = $this->connectToImapTask->getFolder(
|
||||
$client,
|
||||
config('email-news.folder', 'INBOX')
|
||||
);
|
||||
|
||||
// Получаем непрочитанные письма
|
||||
$emails = $this->fetchUnreadEmailsTask->run($folder);
|
||||
|
||||
if (empty($emails)) {
|
||||
Log::info('[FetchEmailNewsAction] Нет непрочитанных писем');
|
||||
return $result;
|
||||
}
|
||||
|
||||
// Фильтруем по отправителю
|
||||
$filteredEmails = $this->filterBySenderTask->run($emails);
|
||||
|
||||
$result['skipped_emails'] = count($emails) - count($filteredEmails);
|
||||
|
||||
// Обрабатываем каждое письмо
|
||||
foreach ($filteredEmails as $email) {
|
||||
$emailResult = $this->processEmail($email, $folder);
|
||||
|
||||
if ($emailResult['success']) {
|
||||
$result['created_posts']++;
|
||||
$result['posts'][] = $emailResult['post'];
|
||||
} else {
|
||||
$result['errors'][] = [
|
||||
'email_subject' => $email['subject'],
|
||||
'error' => $emailResult['error'],
|
||||
];
|
||||
}
|
||||
|
||||
$result['processed_emails']++;
|
||||
}
|
||||
|
||||
Log::info('[FetchEmailNewsAction] Завершено', [
|
||||
'processed' => $result['processed_emails'],
|
||||
'created_posts' => $result['created_posts'],
|
||||
'skipped' => $result['skipped_emails'],
|
||||
'errors_count' => count($result['errors']),
|
||||
]);
|
||||
|
||||
return $result;
|
||||
} catch (\Exception $e) {
|
||||
Log::error('[FetchEmailNewsAction] Критическая ошибка', [
|
||||
'error' => $e->getMessage(),
|
||||
'trace' => $e->getTraceAsString(),
|
||||
]);
|
||||
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Обработать одно письмо
|
||||
*
|
||||
* @param array $email Данные письма
|
||||
* @param Folder $folder IMAP папка
|
||||
* @return array Результат обработки
|
||||
*/
|
||||
private function processEmail(array $email, Folder $folder): array
|
||||
{
|
||||
Log::info('[FetchEmailNewsAction:processEmail] Обработка письма', [
|
||||
'subject' => $email['subject'],
|
||||
'from' => $email['from_email'],
|
||||
]);
|
||||
|
||||
try {
|
||||
// Скачиваем вложения
|
||||
$attachments = $this->downloadAttachmentsTask->run($email['message']);
|
||||
|
||||
// Проверяем, есть ли DOC/DOCX файл
|
||||
$hasDocument = collect($attachments)->contains(fn($att) => $att->isDocument());
|
||||
|
||||
if (!$hasDocument) {
|
||||
Log::warning('[FetchEmailNewsAction:processEmail] Нет DOC/DOCX файла во вложениях', [
|
||||
'subject' => $email['subject'],
|
||||
]);
|
||||
|
||||
return [
|
||||
'success' => false,
|
||||
'error' => 'Нет DOC/DOCX файла для извлечения текста',
|
||||
];
|
||||
}
|
||||
|
||||
// Конвертируем вложения в UploadedFile
|
||||
$uploadedFiles = $this->convertToUploadedFiles($attachments);
|
||||
|
||||
// Обрабатываем через существующий ProcessMixedFilesAction
|
||||
$postResult = $this->processMixedFilesAction->run($uploadedFiles);
|
||||
|
||||
// Помечаем письмо как прочитанное
|
||||
$this->markEmail($email['message'], $folder);
|
||||
|
||||
Log::info('[FetchEmailNewsAction:processEmail] Письмо успешно обработано', [
|
||||
'subject' => $email['subject'],
|
||||
'post_id' => $postResult['post']->id,
|
||||
]);
|
||||
|
||||
return [
|
||||
'success' => true,
|
||||
'post' => $postResult['post'],
|
||||
'attachments_count' => count($attachments),
|
||||
];
|
||||
} catch (\Exception $e) {
|
||||
Log::error('[FetchEmailNewsAction:processEmail] Ошибка обработки письма', [
|
||||
'subject' => $email['subject'],
|
||||
'error' => $e->getMessage(),
|
||||
]);
|
||||
|
||||
return [
|
||||
'success' => false,
|
||||
'error' => $e->getMessage(),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Конвертировать EmailAttachmentData в UploadedFile
|
||||
*
|
||||
* @param array<EmailAttachmentData> $attachments
|
||||
* @return \Illuminate\Support\Collection<UploadedFile>
|
||||
*/
|
||||
private function convertToUploadedFiles(array $attachments): \Illuminate\Support\Collection
|
||||
{
|
||||
$uploadedFiles = [];
|
||||
|
||||
foreach ($attachments as $attachment) {
|
||||
$fullPath = storage_path('app/' . $attachment->path);
|
||||
|
||||
if (!file_exists($fullPath)) {
|
||||
Log::warning('[FetchEmailNewsAction:convertToUploadedFiles] Файл не найден', [
|
||||
'path' => $attachment->path,
|
||||
]);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Создаём UploadedFile из сохранённого файла
|
||||
$uploadedFile = new UploadedFile(
|
||||
$fullPath,
|
||||
$attachment->filename,
|
||||
$attachment->mimeType,
|
||||
null,
|
||||
true // test = false (файл валиден)
|
||||
);
|
||||
|
||||
$uploadedFiles[] = $uploadedFile;
|
||||
}
|
||||
|
||||
Log::info('[FetchEmailNewsAction:convertToUploadedFiles] Конвертировано файлов', [
|
||||
'count' => count($uploadedFiles),
|
||||
]);
|
||||
|
||||
return collect($uploadedFiles);
|
||||
}
|
||||
|
||||
/**
|
||||
* Пометить письмо как прочитанное (и возможно переместить)
|
||||
*
|
||||
* @param object $message IMAP сообщение
|
||||
* @param Folder $folder Текущая папка
|
||||
*/
|
||||
private function markEmail(object $message, Folder $folder): void
|
||||
{
|
||||
$moveToFolder = config('email-news.move_to_folder');
|
||||
|
||||
if ($moveToFolder) {
|
||||
$this->markEmailAsReadTask->markAndMove($message, $moveToFolder);
|
||||
} elseif (config('email-news.mark_as_read', true)) {
|
||||
$this->markEmailAsReadTask->run($message);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
<?php
|
||||
|
||||
namespace App\Containers\Dashboard\Actions\JournalIssues;
|
||||
|
||||
use App\Containers\Science\Models\JournalIssue;
|
||||
|
||||
class CreateJournalIssueAction
|
||||
{
|
||||
public function run(array $data): JournalIssue
|
||||
{
|
||||
return JournalIssue::create($data);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
<?php
|
||||
|
||||
namespace App\Containers\Dashboard\Actions\JournalIssues;
|
||||
|
||||
use App\Containers\Science\Models\JournalIssue;
|
||||
|
||||
class DeleteJournalIssueAction
|
||||
{
|
||||
public function run(JournalIssue $issue): bool
|
||||
{
|
||||
return $issue->delete();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
|
||||
namespace App\Containers\Dashboard\Actions\JournalIssues;
|
||||
|
||||
use App\Containers\Science\Models\JournalIssue;
|
||||
|
||||
class ListJournalIssuesAction
|
||||
{
|
||||
public function run(int $journalId, array $filters = []): array
|
||||
{
|
||||
$query = JournalIssue::where('academic_journal_id', $journalId);
|
||||
|
||||
// Фильтр по году
|
||||
if (!empty($filters['year_publication'])) {
|
||||
$query->where('year_publication', $filters['year_publication']);
|
||||
}
|
||||
|
||||
// Фильтр по статусу
|
||||
if (isset($filters['is_active']) && $filters['is_active'] !== '') {
|
||||
$query->where('is_active', (bool) $filters['is_active']);
|
||||
}
|
||||
|
||||
// Поиск по названию
|
||||
if (!empty($filters['search'])) {
|
||||
$query->where('title', 'like', '%' . $filters['search'] . '%');
|
||||
}
|
||||
|
||||
$issues = $query->orderBy('sort')->orderBy('year_publication', 'desc')->paginate(20)->withQueryString();
|
||||
|
||||
// Получаем уникальные годы для фильтра (оптимизировано через groupBy)
|
||||
$years = JournalIssue::where('academic_journal_id', $journalId)
|
||||
->groupBy('year_publication')
|
||||
->orderBy('year_publication', 'desc')
|
||||
->pluck('year_publication');
|
||||
|
||||
return [
|
||||
'issues' => $issues,
|
||||
'filters' => $filters,
|
||||
'years' => $years,
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
|
||||
namespace App\Containers\Dashboard\Actions\JournalIssues;
|
||||
|
||||
use App\Containers\Science\Models\JournalIssue;
|
||||
|
||||
class UpdateJournalIssueAction
|
||||
{
|
||||
public function run(JournalIssue $issue, array $data): JournalIssue
|
||||
{
|
||||
$issue->update($data);
|
||||
return $issue->fresh();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
namespace App\Containers\Dashboard\Actions;
|
||||
|
||||
use App\Containers\Dashboard\Tasks\Posts\GetAiPreparedPostsTask;
|
||||
use App\Containers\Dashboard\Tasks\Stats\GetDashboardStatsTask;
|
||||
use App\Containers\Dashboard\Tasks\Stats\GetRecentActivityTask;
|
||||
|
||||
class LoadDashboardDataAction
|
||||
{
|
||||
public function __construct(
|
||||
private readonly GetAiPreparedPostsTask $getAiPreparedPostsTask,
|
||||
private readonly GetDashboardStatsTask $getDashboardStatsTask,
|
||||
private readonly GetRecentActivityTask $getRecentActivityTask,
|
||||
) {}
|
||||
|
||||
public function run(): array
|
||||
{
|
||||
return [
|
||||
'aiPreparedPosts' => $this->getAiPreparedPostsTask->run(),
|
||||
'stats' => $this->getDashboardStatsTask->run(),
|
||||
'recentActivity' => $this->getRecentActivityTask->run(),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
namespace App\Containers\Dashboard\Actions\MainSections;
|
||||
|
||||
use App\Containers\AppStructure\Models\MainSection;
|
||||
|
||||
class CreateMainSectionAction
|
||||
{
|
||||
public function run(array $data): MainSection
|
||||
{
|
||||
return MainSection::create([
|
||||
'title' => $data['title'],
|
||||
'slug' => $data['slug'],
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
<?php
|
||||
|
||||
namespace App\Containers\Dashboard\Actions\MainSections;
|
||||
|
||||
use App\Containers\AppStructure\Models\MainSection;
|
||||
|
||||
class DeleteMainSectionAction
|
||||
{
|
||||
public function run(MainSection $mainSection): bool
|
||||
{
|
||||
return $mainSection->delete();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
namespace App\Containers\Dashboard\Actions\MainSections;
|
||||
|
||||
use App\Containers\AppStructure\Models\MainSection;
|
||||
|
||||
class ListMainSectionsAction
|
||||
{
|
||||
public function run(array $filters): array
|
||||
{
|
||||
$query = MainSection::query()->with('subSections');
|
||||
|
||||
if (!empty($filters['search'])) {
|
||||
$query->where('title', 'like', '%' . $filters['search'] . '%');
|
||||
}
|
||||
|
||||
$mainSections = $query->orderBy('sort')->paginate(15);
|
||||
|
||||
return [
|
||||
'mainSections' => $mainSections,
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
namespace App\Containers\Dashboard\Actions\MainSections;
|
||||
|
||||
use App\Containers\AppStructure\Models\MainSection;
|
||||
|
||||
class UpdateMainSectionAction
|
||||
{
|
||||
public function run(MainSection $mainSection, array $data): MainSection
|
||||
{
|
||||
$mainSection->update([
|
||||
'title' => $data['title'],
|
||||
'slug' => $data['slug'],
|
||||
]);
|
||||
|
||||
return $mainSection->fresh();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
namespace App\Containers\Dashboard\Actions\PageReferenceLists;
|
||||
|
||||
use App\Containers\Widget\Models\PageReferenceList;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class CreatePageReferenceListAction
|
||||
{
|
||||
public function run(array $data): PageReferenceList
|
||||
{
|
||||
$data['slug'] = $data['slug'] ?? Str::slug($data['title']);
|
||||
$data['is_active'] = $data['is_active'] ?? true;
|
||||
$data['content'] = $data['content'] ?? [];
|
||||
|
||||
return PageReferenceList::create($data);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
<?php
|
||||
|
||||
namespace App\Containers\Dashboard\Actions\PageReferenceLists;
|
||||
|
||||
use App\Containers\Widget\Models\PageReferenceList;
|
||||
|
||||
class DeletePageReferenceListAction
|
||||
{
|
||||
public function run(PageReferenceList $list): bool
|
||||
{
|
||||
return $list->delete();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
namespace App\Containers\Dashboard\Actions\PageReferenceLists;
|
||||
|
||||
use App\Containers\Widget\Models\PageReferenceList;
|
||||
|
||||
class ListPageReferenceListsAction
|
||||
{
|
||||
public function run(array $filters = []): array
|
||||
{
|
||||
$query = PageReferenceList::query();
|
||||
|
||||
if (!empty($filters['search'])) {
|
||||
$query->where('title', 'like', '%' . $filters['search'] . '%');
|
||||
}
|
||||
|
||||
if (isset($filters['is_active']) && $filters['is_active'] !== '') {
|
||||
$query->where('is_active', (bool) $filters['is_active']);
|
||||
}
|
||||
|
||||
$lists = $query->orderByDesc('created_at')->paginate(20)->withQueryString();
|
||||
|
||||
return [
|
||||
'lists' => $lists,
|
||||
'filters' => $filters,
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
<?php
|
||||
|
||||
namespace App\Containers\Dashboard\Actions\PageReferenceLists;
|
||||
|
||||
use App\Containers\Widget\Models\PageReferenceList;
|
||||
|
||||
class UpdatePageReferenceListAction
|
||||
{
|
||||
public function run(PageReferenceList $list, array $data): PageReferenceList
|
||||
{
|
||||
$list->update($data);
|
||||
|
||||
return $list;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
namespace App\Containers\Dashboard\Actions\Pages;
|
||||
|
||||
use App\Containers\AppStructure\Models\Page;
|
||||
use App\Containers\AppStructure\Models\SubSection;
|
||||
|
||||
class AttachPageToSubSectionAction
|
||||
{
|
||||
public function run(Page $page, SubSection $subSection): Page
|
||||
{
|
||||
// Pessimistic lock to prevent race conditions
|
||||
$page = Page::lockForUpdate()->findOrFail($page->id);
|
||||
|
||||
if ($page->sub_section_id !== null) {
|
||||
throw new \InvalidArgumentException('Страница уже принадлежит другому подразделу');
|
||||
}
|
||||
|
||||
$page->section()->associate($subSection);
|
||||
$page->save();
|
||||
|
||||
return $page;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
<?php
|
||||
|
||||
namespace App\Containers\Dashboard\Actions\Pages;
|
||||
|
||||
use App\Containers\AppStructure\Models\Page;
|
||||
use App\Containers\Dashboard\Tasks\Content\GenerateSearchDataTask;
|
||||
|
||||
class CreatePageAction
|
||||
{
|
||||
public function __construct(
|
||||
private readonly GeneratePagePathAction $generatePagePathAction,
|
||||
private readonly GenerateSearchDataTask $generateSearchDataTask,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Создает новую страницу
|
||||
*
|
||||
* @param array $data Валидированные данные формы (title, slug, sub_section_id, code, searchable, icon, content, settings)
|
||||
* @return Page Созданная страница
|
||||
*/
|
||||
public function run(array $data): Page
|
||||
{
|
||||
$subSectionId = $data['sub_section_id'] ?? null;
|
||||
|
||||
// Генерируем path на основе subSection
|
||||
$data['path'] = $this->generatePagePathAction->run($data['slug'], $subSectionId);
|
||||
|
||||
// Генерируем search_data из контента
|
||||
if (!empty($data['content'])) {
|
||||
$data['search_data'] = $this->generateSearchDataTask->run($data['content']);
|
||||
}
|
||||
|
||||
// Удаляем sub_section_id — он не является полем модели Page
|
||||
unset($data['sub_section_id']);
|
||||
|
||||
return Page::create($data);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
namespace App\Containers\Dashboard\Actions\Pages;
|
||||
|
||||
use App\Containers\AppStructure\Models\Page;
|
||||
|
||||
class DeletePageAction
|
||||
{
|
||||
/**
|
||||
* Удаляет страницу
|
||||
*
|
||||
* @param Page $page Страница для удаления
|
||||
* @return bool Результат удаления
|
||||
*/
|
||||
public function run(Page $page): bool
|
||||
{
|
||||
return $page->delete();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
namespace App\Containers\Dashboard\Actions\Pages;
|
||||
|
||||
use App\Containers\AppStructure\Models\Page;
|
||||
|
||||
class DetachPageFromSubSectionAction
|
||||
{
|
||||
public function run(Page $page): Page
|
||||
{
|
||||
$page->section()->dissociate();
|
||||
$page->save();
|
||||
|
||||
return $page;
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user