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:
F4ilji
2025-04-09 15:58:29 +05:00
parent 7793d078e9
commit 88e729aa87
19 changed files with 684 additions and 100 deletions
+44 -8
View File
@@ -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',
};
}
}
+6 -4
View File
@@ -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 => 'Очно-заочная форма обучения'
};
}
+45
View File
@@ -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();
}),
];
}
}
-10
View File
@@ -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()
{
+139
View File
@@ -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;
}
}
+3 -2
View File
@@ -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;
}
+91 -6
View File
@@ -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
];
}
}
+2 -2
View File
@@ -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) {
+48
View File
@@ -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;
+42 -13
View File
@@ -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
View File
@@ -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');