added API VICON integration for updating admission plans via admin panel (queued).
Updated exams and contests display on programs/show page. Added relevant enums for admission plans.
This commit is contained in:
@@ -7,13 +7,31 @@ use Filament\Support\Contracts\HasColor;
|
||||
|
||||
enum BudgetEducation: int implements HasLabel, HasColor
|
||||
{
|
||||
case budget_quantity_position = 1;
|
||||
case non_budget_quantity_position = 2;
|
||||
case MAIN_QUOTA = 1;
|
||||
case TARGET_QUOTA = 2;
|
||||
case SPECIAL_QUOTA = 3;
|
||||
case PAID_EDUCATION = 4;
|
||||
case OTHER_SOURCES = 5;
|
||||
case SEPARATE_QUOTA = 6;
|
||||
case COMBINED_QUOTA_ALL = 7;
|
||||
case COMBINED_QUOTA_TARGET_SPECIAL = 8;
|
||||
case COMBINED_QUOTA_TARGET_SEPARATE = 9;
|
||||
case COMBINED_QUOTA_SPECIAL_SEPARATE = 10;
|
||||
case DETAILED_TARGET_QUOTA = 11;
|
||||
|
||||
public static function fromName(string $name): ?self {
|
||||
return match ($name) {
|
||||
'budget_quantity_position' => self::budget_quantity_position,
|
||||
'non_budget_quantity_position' => self::non_budget_quantity_position,
|
||||
'MAIN_QUOTA' => self::MAIN_QUOTA,
|
||||
'TARGET_QUOTA' => self::TARGET_QUOTA,
|
||||
'SPECIAL_QUOTA' => self::SPECIAL_QUOTA,
|
||||
'PAID_EDUCATION' => self::PAID_EDUCATION,
|
||||
'OTHER_SOURCES' => self::OTHER_SOURCES,
|
||||
'SEPARATE_QUOTA' => self::SEPARATE_QUOTA,
|
||||
'COMBINED_QUOTA_ALL' => self::COMBINED_QUOTA_ALL,
|
||||
'COMBINED_QUOTA_TARGET_SPECIAL' => self::COMBINED_QUOTA_TARGET_SPECIAL,
|
||||
'COMBINED_QUOTA_TARGET_SEPARATE' => self::COMBINED_QUOTA_TARGET_SEPARATE,
|
||||
'COMBINED_QUOTA_SPECIAL_SEPARATE' => self::COMBINED_QUOTA_SPECIAL_SEPARATE,
|
||||
'DETAILED_TARGET_QUOTA' => self::DETAILED_TARGET_QUOTA,
|
||||
default => null,
|
||||
};
|
||||
}
|
||||
@@ -21,16 +39,34 @@ enum BudgetEducation: int implements HasLabel, HasColor
|
||||
public function getLabel(): ?string
|
||||
{
|
||||
return match ($this) {
|
||||
self::budget_quantity_position => 'Бюджетные места',
|
||||
self::non_budget_quantity_position => 'С оплатой обучения',
|
||||
self::MAIN_QUOTA => 'Основные места',
|
||||
self::TARGET_QUOTA => 'Целевая квота',
|
||||
self::SPECIAL_QUOTA => 'Особая квота',
|
||||
self::PAID_EDUCATION => 'С оплатой обучения',
|
||||
self::OTHER_SOURCES => 'За счёт иных средств',
|
||||
self::SEPARATE_QUOTA => 'Отдельная квота',
|
||||
self::COMBINED_QUOTA_ALL => 'Совмещенная квота (целевая, особая и отдельная квоты)',
|
||||
self::COMBINED_QUOTA_TARGET_SPECIAL => 'Совмещенная квота (целевая и особая квоты)',
|
||||
self::COMBINED_QUOTA_TARGET_SEPARATE => 'Совмещенная квота (целевая и отдельная квоты)',
|
||||
self::COMBINED_QUOTA_SPECIAL_SEPARATE => 'Совмещенная квота (особая и отдельная квоты)',
|
||||
self::DETAILED_TARGET_QUOTA => 'Детализированная целевая квота',
|
||||
};
|
||||
}
|
||||
|
||||
public function getColor(): string|array|null
|
||||
{
|
||||
return match ($this) {
|
||||
self::budget_quantity_position => 'success', // Цвет для очной формы
|
||||
self::non_budget_quantity_position => 'warning', // Цвет для заочной формы
|
||||
self::MAIN_QUOTA => 'primary',
|
||||
self::TARGET_QUOTA => 'info',
|
||||
self::SPECIAL_QUOTA => 'danger',
|
||||
self::PAID_EDUCATION => 'warning',
|
||||
self::OTHER_SOURCES => 'gray',
|
||||
self::SEPARATE_QUOTA => 'success',
|
||||
self::COMBINED_QUOTA_ALL => 'indigo',
|
||||
self::COMBINED_QUOTA_TARGET_SPECIAL => 'violet',
|
||||
self::COMBINED_QUOTA_TARGET_SEPARATE => 'fuchsia',
|
||||
self::COMBINED_QUOTA_SPECIAL_SEPARATE => 'pink',
|
||||
self::DETAILED_TARGET_QUOTA => 'amber',
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -8,16 +8,18 @@ use Filament\Support\Contracts\HasColor;
|
||||
enum FormEducation: int implements HasLabel, HasColor
|
||||
{
|
||||
case FULL_TIME = 1; // Очная форма обучения
|
||||
case PART_TIME = 2; // Заочная форма обучения
|
||||
|
||||
case FULL_PART_TIME = 3; // Заочная форма обучения
|
||||
case FULL_PART_TIME = 2; // Заочная форма обучения
|
||||
|
||||
case PART_TIME = 3; // Заочная форма обучения
|
||||
|
||||
|
||||
|
||||
public static function fromName(string $name): ?self {
|
||||
return match ($name) {
|
||||
'FULL_TIME' => self::FULL_TIME,
|
||||
'PART_TIME' => self::PART_TIME,
|
||||
'FULL_PART_TIME' => self::FULL_PART_TIME,
|
||||
'PART_TIME' => self::PART_TIME,
|
||||
default => null,
|
||||
};
|
||||
}
|
||||
@@ -26,8 +28,8 @@ enum FormEducation: int implements HasLabel, HasColor
|
||||
{
|
||||
return match ($this) {
|
||||
self::FULL_TIME => 'Очная форма обучения',
|
||||
self::FULL_PART_TIME => 'Очно-заочная форма обучения',
|
||||
self::PART_TIME => 'Заочная форма обучения',
|
||||
self::FULL_PART_TIME => 'Очно-заочная форма обучения'
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
<?php
|
||||
|
||||
namespace App\Enums;
|
||||
|
||||
use Filament\Support\Contracts\HasLabel;
|
||||
use Filament\Support\Contracts\HasColor;
|
||||
|
||||
enum TypeExam: int implements HasLabel, HasColor
|
||||
{
|
||||
case EGE = 1;
|
||||
case INTERNAL_EXAM = 2;
|
||||
case AVG_SCORE = 3;
|
||||
case ACCREDITATION = 4;
|
||||
|
||||
public function getLabel(): ?string
|
||||
{
|
||||
return match ($this) {
|
||||
self::EGE => 'ЕГЭ',
|
||||
self::INTERNAL_EXAM => 'ВИ, проводимое организацией самостоятельно',
|
||||
self::AVG_SCORE => 'Ср. балл документа об образовании',
|
||||
self::ACCREDITATION => 'Аккредитация',
|
||||
};
|
||||
}
|
||||
|
||||
public function getColor(): string|array|null
|
||||
{
|
||||
return match ($this) {
|
||||
self::EGE => 'primary',
|
||||
self::INTERNAL_EXAM => 'info',
|
||||
self::AVG_SCORE => 'success',
|
||||
self::ACCREDITATION => 'warning',
|
||||
};
|
||||
}
|
||||
|
||||
public static function fromName(string $name): ?self
|
||||
{
|
||||
return match ($name) {
|
||||
'EGE' => self::EGE,
|
||||
'INTERNAL_EXAM' => self::INTERNAL_EXAM,
|
||||
'AVG_SCORE' => self::AVG_SCORE,
|
||||
'ACCREDITATION' => self::ACCREDITATION,
|
||||
default => null,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -22,32 +22,32 @@ class ListAdmissionCampaigns extends ListRecords
|
||||
->action(function () {
|
||||
// Отправляем запрос в фоновом режиме
|
||||
$this->js(<<<JS
|
||||
fetch('/api/get-data', {
|
||||
fetch('/api/get-edu-program-data', {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').content,
|
||||
},
|
||||
'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').content
|
||||
}
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
.then(() => {
|
||||
// Показываем уведомление об успешном завершении
|
||||
window.dispatchEvent(new CustomEvent('filament-notify', {
|
||||
detail: {
|
||||
type: 'success',
|
||||
title: 'Данные обновлены',
|
||||
body: 'Данные успешно обновлены.',
|
||||
},
|
||||
body: 'Данные успешно обновлены.'
|
||||
}
|
||||
}));
|
||||
})
|
||||
.catch(error => {
|
||||
.catch(() => {
|
||||
// Показываем уведомление об ошибке
|
||||
window.dispatchEvent(new CustomEvent('filament-notify', {
|
||||
detail: {
|
||||
type: 'danger',
|
||||
title: 'Ошибка',
|
||||
body: 'Произошла ошибка при обновлении данных.',
|
||||
},
|
||||
body: 'Произошла ошибка при обновлении данных.'
|
||||
}
|
||||
}));
|
||||
});
|
||||
JS);
|
||||
|
||||
@@ -5,6 +5,7 @@ namespace App\Filament\Resources;
|
||||
use App\Enums\BudgetEducation;
|
||||
use App\Enums\EducationalProgramStatus;
|
||||
use App\Enums\FormEducation;
|
||||
use App\Enums\TypeExam;
|
||||
use App\Filament\Resources\AdmissionPlanResource\Pages;
|
||||
use App\Models\AdmissionCampaign;
|
||||
use App\Models\AdmissionPlan;
|
||||
@@ -82,20 +83,23 @@ class AdmissionPlanResource extends Resource
|
||||
->maxLength(100)
|
||||
->placeholder('Например: Математика')
|
||||
->helperText('Название вступительного испытания'),
|
||||
TextInput::make('priority')
|
||||
->label('Приоритет предмета')
|
||||
->required()
|
||||
->numeric()
|
||||
->columnSpanFull()
|
||||
->maxLength(10),
|
||||
|
||||
Repeater::make('exam')->schema([
|
||||
Select::make('type_exam')
|
||||
Repeater::make('types')->schema([
|
||||
Select::make('type')
|
||||
->label('Тип испытания')
|
||||
->required()
|
||||
->options([
|
||||
'ege' => 'ЕГЭ',
|
||||
'internal_test' => 'Внутреннее испытание',
|
||||
])
|
||||
->options(TypeExam::class)
|
||||
->native(false)
|
||||
->placeholder('Выберите тип')
|
||||
->helperText('Тип вступительного испытания'),
|
||||
|
||||
TextInput::make('min_score')
|
||||
TextInput::make('min_ball')
|
||||
->label('Минимальный балл')
|
||||
->required()
|
||||
->numeric()
|
||||
@@ -134,38 +138,33 @@ class AdmissionPlanResource extends Resource
|
||||
->placeholder('Выберите форму')
|
||||
->columnSpanFull()
|
||||
->helperText('Форма обучения для данной группы'),
|
||||
Section::make()->schema([
|
||||
Select::make('places.form_budget')
|
||||
->label('Форма финансирования')
|
||||
->options(BudgetEducation::class)
|
||||
->required()
|
||||
->native(false)
|
||||
->placeholder('Выберите тип')
|
||||
->helperText('Бюджетные или платные места'),
|
||||
|
||||
Repeater::make('places')
|
||||
->label('Места')
|
||||
->schema([
|
||||
Select::make('form_budget')
|
||||
->label('Форма финансирования')
|
||||
->options(BudgetEducation::class)
|
||||
->required()
|
||||
->native(false)
|
||||
->placeholder('Выберите тип')
|
||||
->helperText('Бюджетные или платные места'),
|
||||
|
||||
TextInput::make('count')
|
||||
->label('Количество мест')
|
||||
->required()
|
||||
->numeric()
|
||||
->minValue(0)
|
||||
->placeholder('Укажите количество')
|
||||
->helperText('Количество доступных мест'),
|
||||
])
|
||||
->columnSpanFull()
|
||||
->maxItems(2)
|
||||
->addActionLabel('Добавить тип мест')
|
||||
TextInput::make('places.count')
|
||||
->label('Количество мест')
|
||||
->required()
|
||||
->numeric()
|
||||
->minValue(0)
|
||||
->placeholder('Укажите количество')
|
||||
->helperText('Количество доступных мест'),
|
||||
]),
|
||||
])
|
||||
->columns(2)
|
||||
->maxItems(3)
|
||||
->maxItems(4) // Adjust if needed based on your actual requirements
|
||||
->collapsible()
|
||||
->collapsed()
|
||||
->addActionLabel('Добавить группу')
|
||||
->helperText('Добавьте группы с условиями поступления');
|
||||
}
|
||||
|
||||
|
||||
public static function table(Table $table): Table
|
||||
{
|
||||
return $table
|
||||
@@ -187,6 +186,9 @@ class AdmissionPlanResource extends Resource
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
public static function getRelations(): array
|
||||
{
|
||||
return [
|
||||
|
||||
@@ -4,6 +4,7 @@ namespace App\Filament\Resources\AdmissionPlanResource\Pages;
|
||||
|
||||
use App\Filament\Resources\AdmissionPlanResource;
|
||||
use Filament\Actions;
|
||||
use Filament\Notifications\Notification;
|
||||
use Filament\Resources\Pages\ListRecords;
|
||||
|
||||
class ListAdmissionPlans extends ListRecords
|
||||
@@ -13,7 +14,52 @@ class ListAdmissionPlans extends ListRecords
|
||||
protected function getHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
Actions\CreateAction::make(),
|
||||
Actions\CreateAction::make(), // Стандартная кнопка "Создать"
|
||||
Actions\Action::make('fetchData') // Кастомная кнопка
|
||||
->label('Обновить данные')
|
||||
->color('primary') // Цвет кнопки
|
||||
->icon('heroicon-o-arrow-path') // Иконка
|
||||
->action(function () {
|
||||
// Отправляем запрос в фоновом режиме
|
||||
$this->js(<<<JS
|
||||
fetch('/api/get-admission-plans-data', {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').content
|
||||
}
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(() => {
|
||||
// Показываем уведомление об успешном завершении
|
||||
window.dispatchEvent(new CustomEvent('filament-notify', {
|
||||
detail: {
|
||||
type: 'success',
|
||||
title: 'Данные обновлены',
|
||||
body: 'Данные успешно обновлены.'
|
||||
}
|
||||
}));
|
||||
})
|
||||
.catch(() => {
|
||||
// Показываем уведомление об ошибке
|
||||
window.dispatchEvent(new CustomEvent('filament-notify', {
|
||||
detail: {
|
||||
type: 'danger',
|
||||
title: 'Ошибка',
|
||||
body: 'Произошла ошибка при обновлении данных.'
|
||||
}
|
||||
}));
|
||||
});
|
||||
JS);
|
||||
|
||||
// Показываем уведомление
|
||||
Notification::make()
|
||||
->title('Данные обновляются')
|
||||
->body('Данные обновляются в фоновом режиме.')
|
||||
->success()
|
||||
->send();
|
||||
}),
|
||||
];
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class IconController extends Controller
|
||||
{
|
||||
//
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Jobs\CreateAdmissionPlan;
|
||||
use App\Jobs\CreateDirectionStudy;
|
||||
use App\Jobs\CreateEducationalProgram;
|
||||
use App\Services\Vicon\EducationalProgram\EducationalProgramService;
|
||||
|
||||
class UpdateAdmissionPlansDataApiController extends Controller
|
||||
{
|
||||
|
||||
public function index()
|
||||
{
|
||||
$this->clearAndCreateAdmissionPlans();
|
||||
return redirect()->route('index');
|
||||
}
|
||||
|
||||
private function clearAndCreateAdmissionPlans() : void
|
||||
{
|
||||
dispatch(new CreateAdmissionPlan());
|
||||
}
|
||||
}
|
||||
@@ -8,12 +8,6 @@ use App\Services\Vicon\EducationalProgram\EducationalProgramService;
|
||||
|
||||
class UpdateEduDataApiController extends Controller
|
||||
{
|
||||
private $programService;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->programService = new EducationalProgramService();
|
||||
}
|
||||
|
||||
public function index()
|
||||
{
|
||||
|
||||
@@ -0,0 +1,139 @@
|
||||
<?php
|
||||
|
||||
namespace App\Jobs;
|
||||
|
||||
use App\Enums\LevelEducational;
|
||||
use App\Models\AdmissionCampaign;
|
||||
use App\Models\DirectionStudy;
|
||||
use App\Models\EducationalProgram;
|
||||
use App\Services\Vicon\DirectionStudy\AdmissionPlanService;
|
||||
use App\Services\Vicon\EducationalProgram\EducationalProgramService;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Foundation\Bus\Dispatchable;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class CreateAdmissionPlan implements ShouldQueue
|
||||
{
|
||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||
|
||||
/**
|
||||
* Create a new job instance.
|
||||
*/
|
||||
|
||||
private AdmissionPlanService $admissionPlanService;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->admissionPlanService = app(AdmissionPlanService::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the job.
|
||||
*/
|
||||
public function handle(): void
|
||||
{
|
||||
try {
|
||||
$items = $this->admissionPlanService->getCampaigns();
|
||||
|
||||
if (empty($items)) {
|
||||
Log::warning('Empty campaigns list received');
|
||||
return; // или обработка пустого случая
|
||||
}
|
||||
|
||||
$campaign = $this->admissionPlanService->findActiveCampaign($items);
|
||||
|
||||
if (empty($campaign)) {
|
||||
Log::warning('No active campaign found in the list');
|
||||
return;
|
||||
}
|
||||
|
||||
$levels_codes = $this->admissionPlanService->getLevelEducationCodes($campaign);
|
||||
|
||||
if (empty($levels_codes)) {
|
||||
Log::warning('No education level codes found for campaign');
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ($levels_codes as $code) {
|
||||
$admissionPlans = $this->admissionPlanService->getAdmissionPlans($code);
|
||||
|
||||
// Проверка на пустой результат или отсутствие нужного свойства
|
||||
if (empty($admissionPlans) || !isset($admissionPlans->competitons_groups)) {
|
||||
Log::warning("Empty admission plans or missing property for code: {$code}", [
|
||||
'admissionPlans' => $admissionPlans
|
||||
]);
|
||||
continue;
|
||||
}
|
||||
|
||||
$filteredPlans = $this->admissionPlanService->filterEmptyNaprOrProg($admissionPlans->competitons_groups);
|
||||
|
||||
if (empty($filteredPlans)) {
|
||||
Log::info("No valid plans after filtering for code: {$code}");
|
||||
continue;
|
||||
}
|
||||
|
||||
$items = $this->admissionPlanService->transformData($filteredPlans);
|
||||
|
||||
if (empty($items)) {
|
||||
Log::info("No items after transformation for code: {$code}");
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach ($items as $item) {
|
||||
// Проверка структуры $item
|
||||
if (!isset($item['id'], $item['plan']->competitions, $item['plan']->exams)) {
|
||||
Log::warning("Invalid item structure", ['item' => $item]);
|
||||
continue;
|
||||
}
|
||||
|
||||
$eduPrograms = EducationalProgram::where('inner_code', $item['id'])->get();
|
||||
|
||||
if ($eduPrograms->isEmpty()) {
|
||||
Log::info("No educational programs found for inner_code: {$item['id']}");
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
$contestData = $this->admissionPlanService->convertToSaveContestData($item['plan']->competitions);
|
||||
$examData = $this->admissionPlanService->convertToSaveExamData($item['plan']->exams);
|
||||
|
||||
foreach ($eduPrograms as $eduProgram) {
|
||||
$eduProgram->admission_plans()->create([
|
||||
'admission_campaigns_id' => $this->getActiveCampaign()->id,
|
||||
'exams' => $examData,
|
||||
'contests' => $contestData,
|
||||
]);
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
Log::error("Error processing admission plan for item: {$item['id']}", [
|
||||
'error' => $e->getMessage(),
|
||||
'item' => $item
|
||||
]);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error in admission plans processing', [
|
||||
'error' => $e->getMessage(),
|
||||
'trace' => $e->getTraceAsString()
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private function getActiveCampaign()
|
||||
{
|
||||
$activeCampaign = AdmissionCampaign::where('status', 1)->first();
|
||||
|
||||
if (!$activeCampaign) {
|
||||
throw new \Exception('No active admission campaign found');
|
||||
}
|
||||
|
||||
return $activeCampaign;
|
||||
}
|
||||
}
|
||||
@@ -18,14 +18,15 @@ class CreateEducationalProgram implements ShouldQueue
|
||||
{
|
||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||
|
||||
private $educatinalProgramService;
|
||||
|
||||
/**
|
||||
* Create a new job instance.
|
||||
*/
|
||||
|
||||
private EducationalProgramService $educatinalProgramService;
|
||||
public function __construct()
|
||||
{
|
||||
$this->educatinalProgramService = new EducationalProgramService();
|
||||
$this->educatinalProgramService = app(EducationalProgramService::class);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -45,7 +45,8 @@ class BreadcrumbService
|
||||
];
|
||||
}
|
||||
|
||||
private function generatePath(string $routeName): ?string
|
||||
|
||||
private function generatePath(?string $routeName): string|null
|
||||
{
|
||||
if ($routeName === 'page.view') {
|
||||
return request()->path();
|
||||
|
||||
@@ -0,0 +1,145 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services\Vicon\DirectionStudy;
|
||||
|
||||
use App\Jobs\CreateDirectionStudy;
|
||||
use App\Jobs\CreateEducationalProgram;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
class AdmissionPlanService
|
||||
{
|
||||
public function getLevelEducationCodes(object $campaign) : array
|
||||
{
|
||||
return array_map(function ($item) {
|
||||
return (int)$item->campaign_levels_code;
|
||||
}, $campaign->groups);
|
||||
}
|
||||
|
||||
public function findActiveCampaign(array $campaigns): object|null
|
||||
{
|
||||
return array_reduce($campaigns, function($carry, $item) {
|
||||
return $carry ?? ($item->status == 1 ? $item : null);
|
||||
});
|
||||
}
|
||||
|
||||
public function getCampaigns(): array
|
||||
{
|
||||
try {
|
||||
$response = $this->callAPI(
|
||||
"https://db-nica.ru/api/v1/campaigns",
|
||||
env('VICON_TOKEN')
|
||||
);
|
||||
if (!is_array($response)) {
|
||||
Log::warning('Unexpected response type in getCampaigns', [
|
||||
'type' => gettype($response),
|
||||
'response' => $response
|
||||
]);
|
||||
}
|
||||
|
||||
return $response;
|
||||
} catch (\Exception $e) {
|
||||
Log::error('API call failed in getCampaigns', [
|
||||
'error' => $e->getMessage(),
|
||||
'trace' => $e->getTraceAsString()
|
||||
]);
|
||||
return []; // или вернуть пустой объект (object)[]
|
||||
}
|
||||
}
|
||||
|
||||
public function getAdmissionPlans(int $campaign_levels_code): object|array
|
||||
{
|
||||
try {
|
||||
$response = $this->callAPI(
|
||||
"https://db-nica.ru/api/v1/planPriema/$campaign_levels_code",
|
||||
env('VICON_TOKEN')
|
||||
);
|
||||
|
||||
if (!is_object($response)) {
|
||||
Log::warning('Unexpected response type in getAdmissionPlans', [
|
||||
'expected' => 'object',
|
||||
'actual' => gettype($response),
|
||||
'campaign_levels_code' => $campaign_levels_code,
|
||||
'response' => $response
|
||||
]);
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
return $response;
|
||||
} catch (\Exception $e) {
|
||||
Log::error('API call failed in getAdmissionPlans', [
|
||||
'error' => $e->getMessage(),
|
||||
'campaign_levels_code' => $campaign_levels_code,
|
||||
'trace' => $e->getTraceAsString()
|
||||
]);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
public function filterEmptyNaprOrProg(array $array): array
|
||||
{
|
||||
return array_values(array_filter($array, function ($item) {
|
||||
return !empty($item->napr_or_prog);
|
||||
}));
|
||||
}
|
||||
|
||||
public function transformData(array $data) : array
|
||||
{
|
||||
return array_map(function ($item) {
|
||||
$array = [];
|
||||
$array['id'] = $item->napr_or_prog[0]->inner_code;
|
||||
$array['plan'] = $item->groups_condition[0];
|
||||
return $array;
|
||||
}, $data);
|
||||
}
|
||||
|
||||
|
||||
public function convertToSaveExamData(array $data) : array
|
||||
{
|
||||
return array_map(function ($item) {
|
||||
$array = [];
|
||||
$array['title'] = $item->exam_name;
|
||||
$array['priority'] = $item->priority;
|
||||
$array['types'] = array_map(function ($i) {
|
||||
return [
|
||||
'type' => $i->type,
|
||||
'min_ball' => $i->min_ball,
|
||||
];
|
||||
}, $item->types);
|
||||
return $array;
|
||||
}, $data);
|
||||
}
|
||||
|
||||
public function convertToSaveContestData(array $data) : array
|
||||
{
|
||||
return array_map(function ($item) {
|
||||
$array = [];
|
||||
$array['form_education'] = $item->form_obuch;
|
||||
$array['places'] = [
|
||||
'form_budget' => $item->source,
|
||||
'count' => $item->count_places
|
||||
];
|
||||
return $array;
|
||||
|
||||
}, $data);
|
||||
}
|
||||
|
||||
private function callAPI(string $endpoint, string $token = null): object|array
|
||||
{
|
||||
try {
|
||||
$response = Http::withToken($token)->get($endpoint);
|
||||
$data = $response->object();
|
||||
|
||||
if (isset($data->message)) {
|
||||
throw new \Exception($data->message);
|
||||
}
|
||||
|
||||
return $data;
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Ошибка при вызове API: ' . $e->getMessage());
|
||||
throw $e; // Перебрасываем исключение
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -20,7 +20,7 @@ class EducationalProgramService
|
||||
$programs = array_merge($programs, $data->rows);
|
||||
}
|
||||
foreach ($programs as $program) {
|
||||
array_push($programsUuid, $program->uuid);
|
||||
$programsUuid[] = $program->uuid;
|
||||
}
|
||||
return $programsUuid;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,80 @@
|
||||
class EducationForm {
|
||||
static BUDGET_QUANTITY_POSITION = { value: 1, label: 'Бюджетные места', color: 'info', name: 'BUDGET_QUANTITY_POSITION' };
|
||||
static NON_BUDGET_QUANTITY_POSITION = { value: 2, label: 'С оплатой обучения', color: 'primary', name: 'NON_BUDGET_QUANTITY_POSITION' };
|
||||
static MAIN_QUOTA = {
|
||||
value: 1,
|
||||
label: 'Основные места',
|
||||
color: 'primary',
|
||||
name: 'MAIN_QUOTA'
|
||||
};
|
||||
|
||||
static TARGET_QUOTA = {
|
||||
value: 2,
|
||||
label: 'Целевая квота',
|
||||
color: 'info',
|
||||
name: 'TARGET_QUOTA'
|
||||
};
|
||||
|
||||
static SPECIAL_QUOTA = {
|
||||
value: 3,
|
||||
label: 'Особая квота',
|
||||
color: 'danger',
|
||||
name: 'SPECIAL_QUOTA'
|
||||
};
|
||||
|
||||
static PAID_EDUCATION = {
|
||||
value: 4,
|
||||
label: 'С оплатой обучения',
|
||||
color: 'warning',
|
||||
name: 'PAID_EDUCATION'
|
||||
};
|
||||
|
||||
static OTHER_SOURCES = {
|
||||
value: 5,
|
||||
label: 'За счёт иных средств',
|
||||
color: 'gray',
|
||||
name: 'OTHER_SOURCES'
|
||||
};
|
||||
|
||||
static SEPARATE_QUOTA = {
|
||||
value: 6,
|
||||
label: 'Отдельная квота',
|
||||
color: 'success',
|
||||
name: 'SEPARATE_QUOTA'
|
||||
};
|
||||
|
||||
static COMBINED_QUOTA_ALL = {
|
||||
value: 7,
|
||||
label: 'Совмещенная квота (целевая, особая и отдельная квоты)',
|
||||
color: 'indigo',
|
||||
name: 'COMBINED_QUOTA_ALL'
|
||||
};
|
||||
|
||||
static COMBINED_QUOTA_TARGET_SPECIAL = {
|
||||
value: 8,
|
||||
label: 'Совмещенная квота (целевая и особая квоты)',
|
||||
color: 'violet',
|
||||
name: 'COMBINED_QUOTA_TARGET_SPECIAL'
|
||||
};
|
||||
|
||||
static COMBINED_QUOTA_TARGET_SEPARATE = {
|
||||
value: 9,
|
||||
label: 'Совмещенная квота (целевая и отдельная квоты)',
|
||||
color: 'fuchsia',
|
||||
name: 'COMBINED_QUOTA_TARGET_SEPARATE'
|
||||
};
|
||||
|
||||
static COMBINED_QUOTA_SPECIAL_SEPARATE = {
|
||||
value: 10,
|
||||
label: 'Совмещенная квота (особая и отдельная квоты)',
|
||||
color: 'pink',
|
||||
name: 'COMBINED_QUOTA_SPECIAL_SEPARATE'
|
||||
};
|
||||
|
||||
static DETAILED_TARGET_QUOTA = {
|
||||
value: 11,
|
||||
label: 'Детализированная целевая квота',
|
||||
color: 'amber',
|
||||
name: 'DETAILED_TARGET_QUOTA'
|
||||
};
|
||||
|
||||
// Метод для получения статуса по имени
|
||||
static fromName(name) {
|
||||
@@ -11,10 +85,21 @@ class EducationForm {
|
||||
return Object.values(this).find(item => item.value == value) || null;
|
||||
}
|
||||
|
||||
|
||||
// Метод для получения имени
|
||||
getName() {
|
||||
return this.name;
|
||||
// Метод для получения всех значений
|
||||
static getAll() {
|
||||
return [
|
||||
this.MAIN_QUOTA,
|
||||
this.TARGET_QUOTA,
|
||||
this.SPECIAL_QUOTA,
|
||||
this.PAID_EDUCATION,
|
||||
this.OTHER_SOURCES,
|
||||
this.SEPARATE_QUOTA,
|
||||
this.COMBINED_QUOTA_ALL,
|
||||
this.COMBINED_QUOTA_TARGET_SPECIAL,
|
||||
this.COMBINED_QUOTA_TARGET_SEPARATE,
|
||||
this.COMBINED_QUOTA_SPECIAL_SEPARATE,
|
||||
this.DETAILED_TARGET_QUOTA
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
class EducationForm {
|
||||
static FULL_TIME = { value: 1, label: 'Очное обучение', color: 'info', name: 'FULL_TIME', type_label: 'Форма обучения' };
|
||||
static PART_TIME = { value: 2, label: 'Заочное обучение', color: 'primary', name: 'PART_TIME', type_label: 'Форма обучения' };
|
||||
static FULL_PART_TIME = { value: 3, label: 'Очно-заочное обучение', color: 'success', name: 'FULL_PART_TIME', type_label: 'Форма обучения' };
|
||||
static FULL_PART_TIME = { value: 2, label: 'Очно-заочное обучение', color: 'success', name: 'FULL_PART_TIME', type_label: 'Форма обучения' };
|
||||
static PART_TIME = { value: 3, label: 'Заочное обучение', color: 'primary', name: 'PART_TIME', type_label: 'Форма обучения' };
|
||||
|
||||
// Метод для получения статуса по имени
|
||||
static fromName(name) {
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
class TypeExam {
|
||||
static EGE = {
|
||||
value: 1,
|
||||
label: 'ЕГЭ',
|
||||
color: 'primary',
|
||||
name: 'EGE'
|
||||
};
|
||||
|
||||
static INTERNAL_EXAM = {
|
||||
value: 2,
|
||||
label: 'Вступительное испытание',
|
||||
color: 'info',
|
||||
name: 'INTERNAL_EXAM'
|
||||
};
|
||||
|
||||
static AVG_SCORE = {
|
||||
value: 3,
|
||||
label: 'Ср. балл аттестата',
|
||||
color: 'success',
|
||||
name: 'AVG_SCORE'
|
||||
};
|
||||
|
||||
static ACCREDITATION = {
|
||||
value: 4,
|
||||
label: 'Аккредитация',
|
||||
color: 'warning',
|
||||
name: 'ACCREDITATION'
|
||||
};
|
||||
|
||||
static fromName(name) {
|
||||
return this[name] || null;
|
||||
}
|
||||
|
||||
static fromValue(value) {
|
||||
return Object.values(this).find(item => item.value == value) || null;
|
||||
}
|
||||
|
||||
static getAll() {
|
||||
return [
|
||||
this.EGE,
|
||||
this.INTERNAL_EXAM,
|
||||
this.AVG_SCORE,
|
||||
this.ACCREDITATION
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
export default TypeExam;
|
||||
@@ -10,6 +10,7 @@ import BasicTitle from "@/componentss/ui/titles/BasicTitle.vue";
|
||||
import ProgramItemBreadcrumbs from "@/componentss/features/educationalPrograms/components/ProgramItemBreadcrumbs.vue";
|
||||
import ProgramTitle from "@/componentss/features/educationalPrograms/components/ProgramTitle.vue";
|
||||
import BudgetEducation from "@/Enum/BudgetEducation.js";
|
||||
import TypeExam from "@/Enum/TypeExam.js";
|
||||
|
||||
|
||||
export default {
|
||||
@@ -21,10 +22,28 @@ export default {
|
||||
BudgetForm() {
|
||||
return BudgetEducation
|
||||
},
|
||||
TypeExam() {
|
||||
return TypeExam
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
typeExam(type) {
|
||||
return (type === 'ege' ? 'ЕГЭ' : 'ВИ')
|
||||
groupExamsByPriority(exams) {
|
||||
return exams.reduce((groups, exam) => {
|
||||
if (!groups[exam.priority]) {
|
||||
groups[exam.priority] = [];
|
||||
}
|
||||
groups[exam.priority].push(exam);
|
||||
return groups;
|
||||
}, {});
|
||||
},
|
||||
groupContestsByFormEducation(contests) {
|
||||
return contests.reduce((groups, contest) => {
|
||||
if (!groups[contest.form_education]) {
|
||||
groups[contest.form_education] = [];
|
||||
}
|
||||
groups[contest.form_education].push(contest);
|
||||
return groups;
|
||||
}, {});
|
||||
}
|
||||
},
|
||||
components: {
|
||||
@@ -119,10 +138,12 @@ export default {
|
||||
<div class="mt-3">
|
||||
<h3 class="text-lg font-semibold text-gray-800">Количество мест на прием</h3>
|
||||
<template v-for="admissionPlan in program.data.admissionPlans">
|
||||
<template v-for="contest in admissionPlan.contests">
|
||||
<template v-for="(contests, form) in groupContestsByFormEducation(admissionPlan.contests)">
|
||||
<div class="border rounded my-2 py-1">
|
||||
<span class="text-gray-500 text-[14px]">{{ EducationForm.fromValue(contest.form_education).label }}</span>
|
||||
<p v-for="place in contest.places" class="mt-1 text-gray-600"> {{ place.count }} мест <span class="text-gray-400 text-[12px]">{{ BudgetForm.fromValue(place.form_budget).label }}</span></p>
|
||||
<span class="text-gray-500 text-[14px]">{{ EducationForm.fromValue(form).label }}</span>
|
||||
<template v-for="contest in contests">
|
||||
<p class="mt-1 text-gray-600"> {{ contest.places.count }} мест <span class="text-gray-400 text-[12px]">{{ BudgetForm.fromValue(contest.places.form_budget).label }}</span></p>
|
||||
</template>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
@@ -148,14 +169,22 @@ export default {
|
||||
</button>
|
||||
<div id="hs-basic-bordered-collapse-two" class="hs-accordion-content hidden w-full overflow-hidden transition-[height] duration-300" aria-labelledby="hs-bordered-heading-two">
|
||||
<div class="pb-4 px-5">
|
||||
<ol class="list-decimal list-inside">
|
||||
<template v-for="admissionPlan in program.data.admissionPlans">
|
||||
<template v-for="exam in admissionPlan.exams">
|
||||
<li>{{ exam.title }} <span v-for="ex in exam.exam" class="text-gray-400 text-[14px]">({{ typeExam(ex.type_exam) }}, минимальный балл: {{ ex.min_score }}) </span></li>
|
||||
</template>
|
||||
</template>
|
||||
</ol>
|
||||
</div>
|
||||
<ol class="list-decimal list-inside">
|
||||
<template v-for="admissionPlan in program.data.admissionPlans">
|
||||
<template v-for="(exams, priority) in groupExamsByPriority(admissionPlan.exams)">
|
||||
<li>
|
||||
<template v-for="(exam, index) in exams">
|
||||
{{ exam.title }}
|
||||
<span v-for="ex in exam.types" class="text-gray-400 text-[14px]">
|
||||
({{ TypeExam.fromValue(ex.type).label }}, минимальный балл: {{ ex.min_ball }})
|
||||
</span>
|
||||
<span class="text-gray-400 text-[14px] uppercase" v-if="index !== exams.length - 1"><br> или </span>
|
||||
</template>
|
||||
</li>
|
||||
</template>
|
||||
</template>
|
||||
</ol>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
+5
-7
@@ -9,17 +9,12 @@ use App\Http\Controllers\ClientWidgetPageController;
|
||||
use App\Http\Controllers\ClientWidgetPageReferenceListController;
|
||||
use App\Http\Controllers\ClientWidgetPostController;
|
||||
use App\Http\Controllers\ClientWidgetSliderController;
|
||||
use App\Http\Controllers\IconController;
|
||||
use App\Http\Controllers\SearchController;
|
||||
use App\Http\Controllers\StaticSearchController;
|
||||
use App\Http\Controllers\UpdateAdmissionPlansDataApiController;
|
||||
use App\Http\Controllers\UpdateEduDataApiController;
|
||||
use App\Http\Controllers\VkAuthController;
|
||||
use App\Http\Controllers\VkPostController;
|
||||
use App\Models\AdmissionCampaign;
|
||||
use App\Services\Vicon\DirectionStudy\DirectionStudyService;
|
||||
use App\Services\VK\VkAuthService;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\Route;
|
||||
|
||||
Route::get('/getAcademicYear', function () {
|
||||
@@ -59,8 +54,11 @@ Route::middleware('ensure.browser')->group(function () {
|
||||
Route::get('/widget/get-slider/{slug}', [ClientWidgetSliderController::class, 'show'])->name('client.widget.slider.show');
|
||||
});
|
||||
|
||||
|
||||
Route::middleware(['auth', 'superadmin'])->group(function () {
|
||||
Route::get('/get-data', [UpdateEduDataApiController::class, 'index']);
|
||||
Route::get('/get-edu-program-data', [UpdateEduDataApiController::class, 'index']);
|
||||
Route::get('/get-admission-plans-data', [UpdateAdmissionPlansDataApiController::class, 'index']);
|
||||
|
||||
|
||||
Route::get('/login/vk', [VkAuthService::class, 'redirectToProvider'])->name('vk.login');
|
||||
Route::get('/login/vk/callback', [VkAuthService::class, 'handleProviderCallback'])->name('vk.callback');
|
||||
|
||||
Reference in New Issue
Block a user