Add artisan commands to import entries from a CSV file
This commit is contained in:
parent
3315efc83b
commit
2dfb745861
|
|
@ -0,0 +1,124 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Console\Commands;
|
||||||
|
|
||||||
|
use App\Models\Audition;
|
||||||
|
use App\Models\Event;
|
||||||
|
use App\Models\Room;
|
||||||
|
use App\Models\ScoringGuide;
|
||||||
|
use App\Services\CsvImportService;
|
||||||
|
use Carbon\Carbon;
|
||||||
|
use Illuminate\Console\Command;
|
||||||
|
|
||||||
|
use function auditionSetting;
|
||||||
|
use function Laravel\Prompts\select;
|
||||||
|
|
||||||
|
class importCheckAuditionsCommand extends Command
|
||||||
|
{
|
||||||
|
protected $signature = 'import:check-auditions';
|
||||||
|
|
||||||
|
protected $description = 'Check the import file for auditions that do not exist in the database';
|
||||||
|
|
||||||
|
protected $csvImporter;
|
||||||
|
|
||||||
|
public function __construct(CsvImportService $csvImporter)
|
||||||
|
{
|
||||||
|
parent::__construct();
|
||||||
|
$this->csvImporter = $csvImporter;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function handle(): void
|
||||||
|
{
|
||||||
|
$lowestPossibleGrade = 1;
|
||||||
|
$highestPossibleGrade = 12;
|
||||||
|
$events = Event::all();
|
||||||
|
$rows = $this->csvImporter->readCsv(storage_path('app/import/import.csv'));
|
||||||
|
$checkedAuditions = collect();
|
||||||
|
foreach ($rows as $row) {
|
||||||
|
if ($checkedAuditions->contains($row['Instrument'])) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$checkedAuditions->push($row['Instrument']);
|
||||||
|
|
||||||
|
if (Audition::where('name', $row['Instrument'])->count() > 0) {
|
||||||
|
$this->info('Audition '.$row['Instrument'].' already exists');
|
||||||
|
} else {
|
||||||
|
$this->newLine();
|
||||||
|
$this->alert('Audition '.$row['Instrument'].' does not exist');
|
||||||
|
if ($events->count() === 1) {
|
||||||
|
$newEventId = $events->first()->id;
|
||||||
|
} else {
|
||||||
|
$newEventId = select(
|
||||||
|
label: 'Which event does this audition belong to?',
|
||||||
|
options: $events->pluck('name', 'id')->toArray(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
$newEventName = $row['Instrument'];
|
||||||
|
$newEventScoreOrder = Audition::max('score_order') + 1;
|
||||||
|
$newEventEntryDeadline = Carbon::yesterday('America/Chicago')->format('Y-m-d');
|
||||||
|
$newEventEntryFee = Audition::max('entry_fee');
|
||||||
|
$newEventMinimumGrade = select(
|
||||||
|
label: 'What is the minimum grade for this audition?',
|
||||||
|
options: range($lowestPossibleGrade, $highestPossibleGrade)
|
||||||
|
);
|
||||||
|
$newEventMaximumGrade = select(
|
||||||
|
label: 'What is the maximum grade for this audition?',
|
||||||
|
options: range($newEventMinimumGrade, $highestPossibleGrade)
|
||||||
|
);
|
||||||
|
$newEventRoomId = select(
|
||||||
|
label: 'Which room does this audition belong to?',
|
||||||
|
options: Room::pluck('name', 'id')->toArray(),
|
||||||
|
);
|
||||||
|
$newEventScoringGuideId = select(
|
||||||
|
label: 'Which scoring guide should this audition use',
|
||||||
|
options: ScoringGuide::pluck('name', 'id')->toArray(),
|
||||||
|
);
|
||||||
|
if (auditionSetting('advanceTo')) {
|
||||||
|
$newEventForSeating = select(
|
||||||
|
label: 'Is this audition for seating?',
|
||||||
|
options: [
|
||||||
|
1 => 'Yes',
|
||||||
|
0 => 'No',
|
||||||
|
]
|
||||||
|
);
|
||||||
|
$newEventForAdvance = select(
|
||||||
|
label: 'Is this audition for '.auditionSetting('advanceTo').'?',
|
||||||
|
options: [
|
||||||
|
1 => 'Yes',
|
||||||
|
0 => 'No',
|
||||||
|
]
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
$newEventForSeating = 1;
|
||||||
|
$newEventForAdvance = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->info('New event ID: '.$newEventId);
|
||||||
|
$this->info('New event name: '.$newEventName);
|
||||||
|
$this->info('New event score order: '.$newEventScoreOrder);
|
||||||
|
$this->info('New event entry deadline: '.$newEventEntryDeadline);
|
||||||
|
$this->info('New event entry fee: '.$newEventEntryFee);
|
||||||
|
$this->info('New event minimum grade: '.$newEventMinimumGrade);
|
||||||
|
$this->info('New event maximum grade: '.$newEventMaximumGrade);
|
||||||
|
$this->info('New event room ID: '.$newEventRoomId);
|
||||||
|
$this->info('New event scoring guide ID: '.$newEventScoringGuideId);
|
||||||
|
$this->info('New event for seating: '.$newEventForSeating);
|
||||||
|
$this->info('New event for advance: '.$newEventForAdvance);
|
||||||
|
|
||||||
|
Audition::create([
|
||||||
|
'event_id' => $newEventId,
|
||||||
|
'name' => $newEventName,
|
||||||
|
'score_order' => $newEventScoreOrder,
|
||||||
|
'entry_deadline' => $newEventEntryDeadline,
|
||||||
|
'entry_fee' => $newEventEntryFee,
|
||||||
|
'minimum_grade' => $newEventMinimumGrade,
|
||||||
|
'maximum_grade' => $newEventMaximumGrade,
|
||||||
|
'room_id' => $newEventRoomId,
|
||||||
|
'scoring_guide_id' => $newEventScoringGuideId,
|
||||||
|
'for_seating' => $newEventForSeating,
|
||||||
|
'for_advancement' => $newEventForAdvance,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,44 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Console\Commands;
|
||||||
|
|
||||||
|
use const PHP_EOL;
|
||||||
|
|
||||||
|
use App\Models\School;
|
||||||
|
use App\Services\CsvImportService;
|
||||||
|
use Illuminate\Console\Command;
|
||||||
|
|
||||||
|
class importCheckSchoolsCommand extends Command
|
||||||
|
{
|
||||||
|
protected $signature = 'import:check-schools';
|
||||||
|
|
||||||
|
protected $description = 'Check the import file for schools that do not exist in the database';
|
||||||
|
|
||||||
|
protected $csvImporter;
|
||||||
|
|
||||||
|
public function __construct(CsvImportService $csvImporter)
|
||||||
|
{
|
||||||
|
parent::__construct();
|
||||||
|
$this->csvImporter = $csvImporter;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function handle(): void
|
||||||
|
{
|
||||||
|
$rows = $this->csvImporter->readCsv(storage_path('app/import/import.csv'));
|
||||||
|
$checkedSchools = collect();
|
||||||
|
foreach ($rows as $row) {
|
||||||
|
if ($checkedSchools->contains($row['School'])) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$checkedSchools->push($row['School']);
|
||||||
|
if (School::where('name', $row['School'])->count() > 0) {
|
||||||
|
$this->info('School '.$row['School'].' already exists');
|
||||||
|
} else {
|
||||||
|
$this->newLine();
|
||||||
|
$this->alert('School '.$row['School'].' does not exist'.PHP_EOL.'Creating school...');
|
||||||
|
School::create(['name' => $row['School']]);
|
||||||
|
$this->info('School '.$row['School'].' created');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,67 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Console\Commands;
|
||||||
|
|
||||||
|
use App\Models\Entry;
|
||||||
|
use App\Models\School;
|
||||||
|
use App\Models\Student;
|
||||||
|
use App\Services\CsvImportService;
|
||||||
|
use Illuminate\Console\Command;
|
||||||
|
|
||||||
|
class importCheckStudentsCommand extends Command
|
||||||
|
{
|
||||||
|
protected $signature = 'import:check-students';
|
||||||
|
|
||||||
|
protected $description = 'Check the import file for students that do not exist in the database';
|
||||||
|
|
||||||
|
protected $csvImporter;
|
||||||
|
|
||||||
|
public function __construct(CsvImportService $csvImporter)
|
||||||
|
{
|
||||||
|
parent::__construct();
|
||||||
|
$this->csvImporter = $csvImporter;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function handle(): void
|
||||||
|
{
|
||||||
|
$purge = $this->confirm('Do you want to purge the database of existing students and entries?', false);
|
||||||
|
if ($purge) {
|
||||||
|
Entry::all()->map(function ($entry) {
|
||||||
|
$entry->delete();
|
||||||
|
});
|
||||||
|
Student::all()->map(function ($student) {
|
||||||
|
$student->delete();
|
||||||
|
});
|
||||||
|
$this->info('Database purged');
|
||||||
|
}
|
||||||
|
$schools = School::pluck('id', 'name');
|
||||||
|
$rows = $this->csvImporter->readCsv(storage_path('app/import/import.csv'));
|
||||||
|
$checkedStudents = collect();
|
||||||
|
foreach ($rows as $row) {
|
||||||
|
$uniqueData = $row['School'].$row['LastName'].$row['LastName'];
|
||||||
|
if ($checkedStudents->contains($uniqueData)) {
|
||||||
|
// continue;
|
||||||
|
}
|
||||||
|
$checkedStudents->push($uniqueData);
|
||||||
|
|
||||||
|
$currentFirstName = $row['FirstName'];
|
||||||
|
$currentLastName = $row['LastName'];
|
||||||
|
$currentSchoolName = $row['School'];
|
||||||
|
$currentSchoolId = $schools[$currentSchoolName];
|
||||||
|
|
||||||
|
if (Student::where('first_name', $currentFirstName)->where('last_name',
|
||||||
|
$currentLastName)->where('school_id', $currentSchoolId)->count() > 0) {
|
||||||
|
$this->info('Student '.$currentFirstName.' '.$currentLastName.' from '.$currentSchoolName.' already exists');
|
||||||
|
} else {
|
||||||
|
$this->alert('Student '.$currentFirstName.' '.$currentLastName.' from '.$currentSchoolName.' does not exist');
|
||||||
|
$newStudent = Student::create([
|
||||||
|
'school_id' => $currentSchoolId,
|
||||||
|
'first_name' => $currentFirstName,
|
||||||
|
'last_name' => $currentLastName,
|
||||||
|
'grade' => $row['Grade'],
|
||||||
|
]);
|
||||||
|
$this->info('Student '.$currentFirstName.' '.$currentLastName.' from '.$currentSchoolName.' created with id of: '.$newStudent->id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,74 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Console\Commands;
|
||||||
|
|
||||||
|
use App\Models\Audition;
|
||||||
|
use App\Models\Entry;
|
||||||
|
use App\Models\School;
|
||||||
|
use App\Models\Student;
|
||||||
|
use App\Services\CsvImportService;
|
||||||
|
use Illuminate\Console\Command;
|
||||||
|
|
||||||
|
class importImportEntriesCommand extends Command
|
||||||
|
{
|
||||||
|
protected $signature = 'import';
|
||||||
|
|
||||||
|
protected $description = 'Import entries from the import.csv file. First check schools, then students, then auditions, then run this import command';
|
||||||
|
|
||||||
|
protected $csvImporter;
|
||||||
|
|
||||||
|
public function __construct(CsvImportService $csvImporter)
|
||||||
|
{
|
||||||
|
parent::__construct();
|
||||||
|
$this->csvImporter = $csvImporter;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function handle(): void
|
||||||
|
{
|
||||||
|
$checkAuditions = $this->confirm('Do you want to check the auditions in the import for validity first?', true);
|
||||||
|
if ($checkAuditions) {
|
||||||
|
$this->call('import:check-auditions');
|
||||||
|
}
|
||||||
|
|
||||||
|
$checkSchools = $this->confirm('Do you want to check the schools in the import for validity first?', true);
|
||||||
|
if ($checkSchools) {
|
||||||
|
$this->call('import:check-schools');
|
||||||
|
}
|
||||||
|
|
||||||
|
$checkStudents = $this->confirm('Do you want to check the students in the import for validity first?', true);
|
||||||
|
if ($checkStudents) {
|
||||||
|
$this->call('import:check-students');
|
||||||
|
}
|
||||||
|
|
||||||
|
$purge = $this->confirm('Do you want to purge the database of existing entries?', false);
|
||||||
|
if ($purge) {
|
||||||
|
Entry::all()->map(function ($entry) {
|
||||||
|
$entry->delete();
|
||||||
|
});
|
||||||
|
$this->info('Database purged');
|
||||||
|
}
|
||||||
|
$schools = School::pluck('id', 'name');
|
||||||
|
$auditions = Audition::pluck('id', 'name');
|
||||||
|
$rows = $this->csvImporter->readCsv(storage_path('app/import/import.csv'));
|
||||||
|
foreach ($rows as $row) {
|
||||||
|
$schoolId = $schools[$row['School']];
|
||||||
|
$student = Student::where('first_name', $row['FirstName'])->where('last_name',
|
||||||
|
$row['LastName'])->where('school_id', $schoolId)->first();
|
||||||
|
if (! $student) {
|
||||||
|
$this->error('Student '.$row['FirstName'].' '.$row['LastName'].' from '.$row['School'].' does not exist');
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$auditionId = $auditions[$row['Instrument']];
|
||||||
|
try {
|
||||||
|
Entry::create([
|
||||||
|
'student_id' => $student->id,
|
||||||
|
'audition_id' => $auditionId,
|
||||||
|
]);
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
$this->warn('Entry already exists for student '.$student->full_name().' in audition '.$row['Instrument']);
|
||||||
|
}
|
||||||
|
$this->info('Entry created for student '.$student->full_name().' in audition '.$row['Instrument']);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,42 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Services;
|
||||||
|
|
||||||
|
class CsvImportService
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Read a CSV file and return its contents as an array
|
||||||
|
*
|
||||||
|
* @param string $filePath Full path to the CSV file
|
||||||
|
* @param bool $trimHeaders Whether to trim whitespace from header names
|
||||||
|
* @return array Array of rows with header keys
|
||||||
|
*/
|
||||||
|
public function readCsv(string $filePath, bool $trimHeaders = true): array
|
||||||
|
{
|
||||||
|
if (! file_exists($filePath)) {
|
||||||
|
throw new \RuntimeException("File not found: {$filePath}");
|
||||||
|
}
|
||||||
|
|
||||||
|
$handle = fopen($filePath, 'r');
|
||||||
|
if ($handle === false) {
|
||||||
|
throw new \RuntimeException("Unable to open file: {$filePath}");
|
||||||
|
}
|
||||||
|
|
||||||
|
$header = null;
|
||||||
|
$rows = [];
|
||||||
|
|
||||||
|
while (($line = fgetcsv($handle, 0, ',')) !== false) {
|
||||||
|
if (! $header) {
|
||||||
|
$header = $trimHeaders ? array_map('trim', $line) : $line;
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$row = array_combine($header, $line);
|
||||||
|
$rows[] = $row;
|
||||||
|
}
|
||||||
|
|
||||||
|
fclose($handle);
|
||||||
|
|
||||||
|
return $rows;
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue