Files
ntspi-app/app/Containers/Dashboard/Actions/Schedules/UploadMultipleSchedulesAction.php
T
2026-04-07 17:54:26 +05:00

198 lines
8.6 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
namespace App\Containers\Dashboard\Actions\Schedules;
use App\Containers\Schedule\Models\EducationalGroup;
use App\Containers\Schedule\Models\Schedule;
use Illuminate\Support\Str;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Carbon;
class UploadMultipleSchedulesAction
{
private array $processedFiles = [];
private array $failedFiles = [];
/**
* Обрабатывает множественную загрузку файлов расписаний
*
* @param array $files Массив загруженных файлов
* @return array Результаты обработки
*/
public function run(array $files): array
{
$this->processedFiles = [];
$this->failedFiles = [];
foreach ($files as $file) {
$this->processFile($file);
}
return [
'processed' => $this->processedFiles,
'failed' => $this->failedFiles,
'processed_count' => count($this->processedFiles),
'failed_count' => count($this->failedFiles),
];
}
/**
* Обрабатывает одиночный файл
*/
private function processFile($file): void
{
try {
$originalFileNameWithExtension = $file->getClientOriginalName();
$originalFileName = pathinfo($originalFileNameWithExtension, PATHINFO_FILENAME);
// Проверяем, был ли файл уже обработан
if (in_array($originalFileNameWithExtension, array_column($this->processedFiles, 'filename'))) {
return;
}
$educationalGroup = $this->findEducationalGroupByFileName($originalFileName);
if (!$educationalGroup) {
// Извлекаем очищенное название для более понятного сообщения об ошибке
$fileNameWithoutExt = pathinfo($originalFileName, PATHINFO_FILENAME);
$cleanedName = preg_replace('/\s+с\s+\d{2}\.\d{2}(\.\d{4})?\s+по\s+\d{2}\.\d{2}(\.\d{4})?/ui', '', $fileNameWithoutExt);
$cleanedName = preg_replace('/\s*(Экзамены|расписание|сессия|контрольная|\d{4}).*$/ui', '', $cleanedName);
$cleanedName = trim(preg_replace('/\s+/', ' ', $cleanedName));
$this->failedFiles[] = [
'filename' => $originalFileNameWithExtension,
'error' => "Группа '{$cleanedName}' не найдена в базе. Проверьте правильность названия группы.",
];
return;
}
// Проверяем наличие расписания для данной группы
$existingSchedule = Schedule::where('educational_group_id', $educationalGroup->id)
->whereJsonContains('file->0->title', $originalFileName)
->first();
if ($existingSchedule) {
$this->failedFiles[] = [
'filename' => $originalFileNameWithExtension,
'error' => 'Расписание уже существует',
];
return;
}
// Сохраняем файл
$uniqueFileName = Str::slug($originalFileName) . '-' . Carbon::now()->timestamp . '.' . $file->getClientOriginalExtension();
$path = $file->storeAs('schedules', $uniqueFileName, 'public');
// Создаем запись
DB::transaction(function () use ($educationalGroup, $originalFileName, $path) {
Schedule::create([
'educational_group_id' => $educationalGroup->id,
'file' => [
[
'title' => $originalFileName,
'path' => $path,
],
],
]);
});
$this->processedFiles[] = [
'filename' => $originalFileNameWithExtension,
'group' => $educationalGroup->title,
];
} catch (\Throwable $e) {
$this->failedFiles[] = [
'filename' => $file->getClientOriginalName(),
'error' => $e->getMessage(),
];
}
}
/**
* Находит образовательную группу по названию файла
*
* Алгоритм:
* 1. Очищаем название файла от лишней информации (даты, слова "Экзамены", "расписание" и т.д.)
* 2. Извлекаем код группы по паттернам
* 3. Ищем точное совпадение в базе
*/
private function findEducationalGroupByFileName(string $fileName): ?EducationalGroup
{
$fileNameWithoutExt = pathinfo($fileName, PATHINFO_FILENAME);
// Нормализуем имя файла - убираем лишние пробелы
$fileNameWithoutExt = preg_replace('/\s+/', ' ', trim($fileNameWithoutExt));
// ========== ШАГ 1: Очищаем название файла от лишней информации ==========
// Удаляем даты в форматах: "с 06.04 по 09.04.2026", "с 10.01.2026 по 15.01.2026"
$cleanedName = preg_replace('/\s+с\s+\d{2}\.\d{2}(\.\d{4})?\s+по\s+\d{2}\.\d{2}(\.\d{4})?/ui', '', $fileNameWithoutExt);
// Удаляем слова "Экзамены", "расписание", "сессия" и годы
$cleanedName = preg_replace('/\s*(Экзамены|расписание|сессия|контрольная|\d{4}).*$/ui', '', $cleanedName);
// Финальная очистка - убираем лишние пробелы
$cleanedName = trim(preg_replace('/\s+/', ' ', $cleanedName));
// ========== ШАГ 2: Извлечение кода группы по паттернам ==========
// Паттерн 1: "Нт-XXXо YYY" или "Нт-XXXоYYY" (очная форма, например: Нт-101о ИРЭ, Нт-211Со Экл)
if (preg_match('/^(Нт-\d{2,3}[а-яА-ЯёЁ]?\s*[а-яА-ЯёЁ]{2,5})/u', $cleanedName, $matches)) {
$groupCode = trim($matches[1]);
// Нормализуем: убираем лишние пробелы между номером и буквами
$groupCode = preg_replace('/(\d)(\s+)([а-яА-ЯёЁ])/u', '$1$3', $groupCode);
// Ищем ТОЧНОЕ совпадение
$group = EducationalGroup::where('title', $groupCode)->first();
if ($group) {
return $group;
}
}
// Паттерн 2: "Нт-XXX YYY" (заочная форма, например: Нт-404 ИП, Нт-501 СР)
if (preg_match('/^(Нт-\d{3}\s+[А-Я]{2,4})(?![а-яА-Я])/u', $cleanedName, $matches)) {
$groupCode = trim($matches[1]);
$group = EducationalGroup::where('title', $groupCode)->first();
if ($group) {
return $group;
}
}
// Паттерн 3: "нXXX YY-о3" (например: н101 ГД-о3, н303 ФК-По3)
if (preg_match('/^(н\d{3}\s+[А-Яа-я\-]+-о[34])/u', $cleanedName, $matches)) {
$groupCode = trim($matches[1]);
$group = EducationalGroup::where('title', $groupCode)->first();
if ($group) {
return $group;
}
}
// Паттерн 4: "Нт-XXX мYYY" (магистратура, например: Нт-102 мТМОД)
if (preg_match('/^(Нт-\d{3}\s+м[А-Я]{2,5})/u', $cleanedName, $matches)) {
$groupCode = trim($matches[1]);
$group = EducationalGroup::where('title', $groupCode)->first();
if ($group) {
return $group;
}
}
// Паттерн 5: "БФКф-XXXX" (например: БФКф-2531)
if (preg_match('/^(БФКф-\d{4})/u', $cleanedName, $matches)) {
$groupCode = trim($matches[1]);
$group = EducationalGroup::where('title', $groupCode)->first();
if ($group) {
return $group;
}
}
// ========== ШАГ 3: Точное совпадение по очищенному имени файла ==========
$exactMatch = EducationalGroup::where('title', $cleanedName)->first();
if ($exactMatch) {
return $exactMatch;
}
return null;
}
}