affiliations
This commit is contained in:
1 parent
4f3f9c9aad
commit
0b5b3dd3ee
177 files changed
+9660
-8531
No files matched your search
@@ -0,0 +1,60 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::table('skill_acquisitions', function (Blueprint $table) {
|
||||
// Add start and end dates for skill training
|
||||
$table->date('start_date')->nullable()->after('duration_months');
|
||||
$table->date('end_date')->nullable()->after('start_date');
|
||||
|
||||
// Link to package, activity, and instructor
|
||||
$table->foreignId('package_id')->nullable()->after('club_affiliation_id')->constrained('club_packages')->nullOnDelete();
|
||||
$table->foreignId('activity_id')->nullable()->after('package_id')->constrained('club_activities')->nullOnDelete();
|
||||
$table->foreignId('instructor_id')->nullable()->after('activity_id')->constrained('club_instructors')->nullOnDelete();
|
||||
|
||||
// Add notes about the skill acquisition
|
||||
$table->text('notes')->nullable()->after('proficiency_level');
|
||||
|
||||
// Add indexes
|
||||
$table->index('start_date');
|
||||
$table->index('end_date');
|
||||
$table->index('package_id');
|
||||
$table->index('instructor_id');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('skill_acquisitions', function (Blueprint $table) {
|
||||
$table->dropForeign(['package_id']);
|
||||
$table->dropForeign(['activity_id']);
|
||||
$table->dropForeign(['instructor_id']);
|
||||
|
||||
$table->dropIndex(['start_date']);
|
||||
$table->dropIndex(['end_date']);
|
||||
$table->dropIndex(['package_id']);
|
||||
$table->dropIndex(['instructor_id']);
|
||||
|
||||
$table->dropColumn([
|
||||
'start_date',
|
||||
'end_date',
|
||||
'package_id',
|
||||
'activity_id',
|
||||
'instructor_id',
|
||||
'notes'
|
||||
]);
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::table('club_member_subscriptions', function (Blueprint $table) {
|
||||
// Link subscription to club affiliation
|
||||
$table->foreignId('club_affiliation_id')->nullable()->after('user_id')->constrained('club_affiliations')->nullOnDelete();
|
||||
|
||||
$table->index('club_affiliation_id');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('club_member_subscriptions', function (Blueprint $table) {
|
||||
$table->dropForeign(['club_affiliation_id']);
|
||||
$table->dropIndex(['club_affiliation_id']);
|
||||
$table->dropColumn('club_affiliation_id');
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('instructor_reviews', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->foreignId('instructor_id')->constrained('club_instructors')->onDelete('cascade');
|
||||
$table->foreignId('reviewer_user_id')->constrained('users')->onDelete('cascade');
|
||||
$table->tinyInteger('rating')->unsigned()->comment('1-5 stars');
|
||||
$table->text('comment')->nullable();
|
||||
$table->timestamp('reviewed_at');
|
||||
$table->timestamps();
|
||||
|
||||
// Ensure one review per user per instructor
|
||||
$table->unique(['instructor_id', 'reviewer_user_id']);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('instructor_reviews');
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,297 @@
|
||||
<?php
|
||||
|
||||
namespace Database\Seeders;
|
||||
|
||||
use Illuminate\Database\Seeder;
|
||||
use App\Models\User;
|
||||
use App\Models\Tenant;
|
||||
use App\Models\ClubAffiliation;
|
||||
use App\Models\SkillAcquisition;
|
||||
use App\Models\AffiliationMedia;
|
||||
use App\Models\ClubPackage;
|
||||
use App\Models\ClubActivity;
|
||||
use App\Models\ClubInstructor;
|
||||
use App\Models\ClubMemberSubscription;
|
||||
use App\Models\ClubPackageActivity;
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
class AffiliationsDataSeeder extends Seeder
|
||||
{
|
||||
/**
|
||||
* Run the database seeds.
|
||||
*/
|
||||
public function run(): void
|
||||
{
|
||||
// Sample clubs/tenants
|
||||
$clubs = [
|
||||
['name' => 'Elite Martial Arts Academy', 'location' => 'Manama, Bahrain'],
|
||||
['name' => 'Champions Boxing Club', 'location' => 'Riffa, Bahrain'],
|
||||
['name' => 'Fitness First Gym', 'location' => 'Seef, Bahrain'],
|
||||
['name' => 'Warrior Taekwondo Center', 'location' => 'Muharraq, Bahrain'],
|
||||
];
|
||||
|
||||
// Skills should be the main sport/martial art, not subdivisions
|
||||
$martialArtsSkills = [
|
||||
'Taekwondo',
|
||||
'Boxing',
|
||||
'Karate',
|
||||
'Kickboxing',
|
||||
'Muay Thai',
|
||||
'Jiu-Jitsu',
|
||||
'Judo',
|
||||
'Wrestling',
|
||||
'MMA'
|
||||
];
|
||||
|
||||
$fitnessSkills = [
|
||||
'Strength Training',
|
||||
'Cardio Training',
|
||||
'CrossFit',
|
||||
'Functional Training',
|
||||
'Yoga',
|
||||
'Pilates',
|
||||
'Calisthenics'
|
||||
];
|
||||
|
||||
$instructorNames = [
|
||||
'Master Ahmed Al-Khalifa', 'Coach Sarah Johnson', 'Sensei Mohammed Ali',
|
||||
'Coach David Martinez', 'Master Fatima Hassan', 'Coach John Smith',
|
||||
'Instructor Lisa Chen', 'Coach Omar Abdullah', 'Master Kim Lee',
|
||||
'Coach Maria Garcia'
|
||||
];
|
||||
|
||||
// Get all users
|
||||
$users = User::all();
|
||||
|
||||
if ($users->isEmpty()) {
|
||||
$this->command->warn('No users found. Please create users first.');
|
||||
return;
|
||||
}
|
||||
|
||||
$this->command->info('Starting to seed affiliations data for ' . $users->count() . ' users...');
|
||||
|
||||
foreach ($users as $user) {
|
||||
// Track skills acquired by this user (each skill only once in lifetime)
|
||||
$userSkillsAcquired = [];
|
||||
|
||||
// Each user gets 2-4 club affiliations
|
||||
$affiliationCount = rand(2, 4);
|
||||
|
||||
$this->command->info("Creating {$affiliationCount} affiliations for user: {$user->full_name}");
|
||||
|
||||
for ($i = 0; $i < $affiliationCount; $i++) {
|
||||
$clubData = $clubs[array_rand($clubs)];
|
||||
|
||||
// Create dates - older affiliations first
|
||||
$yearsAgo = $affiliationCount - $i;
|
||||
$startDate = Carbon::now()->subYears($yearsAgo)->subMonths(rand(0, 11));
|
||||
|
||||
// Some affiliations are ongoing (no end date)
|
||||
$isOngoing = ($i === 0 && rand(0, 1) === 1); // 50% chance first affiliation is ongoing
|
||||
$endDate = $isOngoing ? null : $startDate->copy()->addMonths(rand(6, 24));
|
||||
|
||||
// Get or create tenant for this club
|
||||
$tenant = Tenant::firstOrCreate(
|
||||
['club_name' => $clubData['name']],
|
||||
[
|
||||
'owner_user_id' => 1, // Assuming admin user
|
||||
'club_name' => $clubData['name'],
|
||||
'slug' => \Illuminate\Support\Str::slug($clubData['name']),
|
||||
'address' => $clubData['location'],
|
||||
'country' => 'Bahrain',
|
||||
]
|
||||
);
|
||||
|
||||
// Create club affiliation
|
||||
$affiliation = ClubAffiliation::create([
|
||||
'member_id' => $user->id,
|
||||
'club_name' => $clubData['name'],
|
||||
'start_date' => $startDate,
|
||||
'end_date' => $endDate,
|
||||
'location' => $clubData['location'],
|
||||
'coaches' => array_rand(array_flip($instructorNames), rand(2, 3)),
|
||||
'description' => 'Member of ' . $clubData['name'] . ' training in various disciplines.',
|
||||
]);
|
||||
|
||||
$this->command->info(" - Created affiliation: {$clubData['name']} ({$startDate->format('Y-m-d')} to " . ($endDate ? $endDate->format('Y-m-d') : 'Present') . ")");
|
||||
|
||||
// Create instructors for this club
|
||||
$clubInstructors = [];
|
||||
foreach (array_rand(array_flip($instructorNames), rand(3, 5)) as $instructorName) {
|
||||
// Create a user for the instructor if not exists
|
||||
$instructorUser = User::firstOrCreate(
|
||||
['email' => strtolower(str_replace(' ', '.', $instructorName)) . '@club.com'],
|
||||
[
|
||||
'name' => $instructorName,
|
||||
'full_name' => $instructorName,
|
||||
'password' => bcrypt('password'),
|
||||
'gender' => 'm',
|
||||
]
|
||||
);
|
||||
|
||||
$instructor = ClubInstructor::firstOrCreate(
|
||||
[
|
||||
'tenant_id' => $tenant->id,
|
||||
'user_id' => $instructorUser->id,
|
||||
],
|
||||
[
|
||||
'role' => ['Martial Arts', 'Fitness', 'Boxing'][rand(0, 2)] . ' Instructor',
|
||||
'experience_years' => rand(5, 15),
|
||||
'rating' => rand(40, 50) / 10, // 4.0 to 5.0
|
||||
'skills' => json_encode(['Coaching', 'Training', 'Mentoring']),
|
||||
'bio' => 'Experienced instructor with over 10 years of teaching experience.',
|
||||
]
|
||||
);
|
||||
$clubInstructors[] = $instructor;
|
||||
}
|
||||
|
||||
// Create 2-3 packages for this affiliation
|
||||
$packageCount = rand(2, 3);
|
||||
for ($p = 0; $p < $packageCount; $p++) {
|
||||
$packageStartDate = $startDate->copy()->addMonths($p * 6);
|
||||
$packageEndDate = $packageStartDate->copy()->addMonths(rand(3, 6));
|
||||
|
||||
// Don't create packages beyond affiliation end date
|
||||
if ($endDate && $packageStartDate->gt($endDate)) {
|
||||
break;
|
||||
}
|
||||
|
||||
$package = ClubPackage::create([
|
||||
'tenant_id' => $tenant->id,
|
||||
'name' => ['Beginner Package', 'Intermediate Package', 'Advanced Package', 'Elite Training'][rand(0, 3)],
|
||||
'type' => ['single', 'multi'][rand(0, 1)],
|
||||
'age_min' => $user->age - 5,
|
||||
'age_max' => $user->age + 5,
|
||||
'gender' => 'mixed',
|
||||
'price' => rand(30, 150),
|
||||
'duration_months' => rand(1, 6),
|
||||
'session_count' => rand(12, 48),
|
||||
'description' => 'Comprehensive training package',
|
||||
'is_active' => true,
|
||||
]);
|
||||
|
||||
// Create 2-4 activities for this package
|
||||
$activityCount = rand(2, 4);
|
||||
$packageActivities = [];
|
||||
|
||||
for ($a = 0; $a < $activityCount; $a++) {
|
||||
$activity = ClubActivity::create([
|
||||
'tenant_id' => $tenant->id,
|
||||
'name' => ['Martial Arts', 'Boxing', 'Fitness'][rand(0, 2)] . ' Class ' . chr(65 + $a),
|
||||
'duration_minutes' => [45, 60, 90][rand(0, 2)],
|
||||
'frequency_per_week' => rand(2, 4),
|
||||
'schedule' => [
|
||||
['day' => 'Monday', 'time' => '16:00'],
|
||||
['day' => 'Wednesday', 'time' => '16:00'],
|
||||
['day' => 'Saturday', 'time' => '10:00'],
|
||||
],
|
||||
'description' => 'Regular training sessions',
|
||||
]);
|
||||
|
||||
// Link activity to package with instructor
|
||||
$instructor = $clubInstructors[array_rand($clubInstructors)];
|
||||
ClubPackageActivity::create([
|
||||
'package_id' => $package->id,
|
||||
'activity_id' => $activity->id,
|
||||
'instructor_id' => $instructor->id,
|
||||
]);
|
||||
|
||||
$packageActivities[] = [
|
||||
'activity' => $activity,
|
||||
'instructor' => $instructor,
|
||||
];
|
||||
}
|
||||
|
||||
// Create subscription for this package
|
||||
// Make sure tenant exists before creating subscription
|
||||
if ($tenant && $tenant->id) {
|
||||
$subscription = ClubMemberSubscription::create([
|
||||
'tenant_id' => $tenant->id,
|
||||
'user_id' => $user->id,
|
||||
'club_affiliation_id' => $affiliation->id,
|
||||
'package_id' => $package->id,
|
||||
'start_date' => $packageStartDate,
|
||||
'end_date' => $packageEndDate,
|
||||
'status' => $packageEndDate->lt(Carbon::now()) ? 'expired' : 'active',
|
||||
'payment_status' => 'paid',
|
||||
'amount_paid' => $package->price,
|
||||
'amount_due' => 0,
|
||||
]);
|
||||
}
|
||||
|
||||
// Create skills from this package's activities
|
||||
foreach ($packageActivities as $pa) {
|
||||
$activity = $pa['activity'];
|
||||
$instructor = $pa['instructor'];
|
||||
|
||||
// Determine skill category based on activity name
|
||||
$skillPool = str_contains($activity->name, 'Fitness') ? $fitnessSkills : $martialArtsSkills;
|
||||
|
||||
// Each activity teaches 1-3 skills
|
||||
$skillsToTeach = array_rand(array_flip($skillPool), rand(1, 3));
|
||||
if (!is_array($skillsToTeach)) {
|
||||
$skillsToTeach = [$skillsToTeach];
|
||||
}
|
||||
|
||||
foreach ($skillsToTeach as $skillName) {
|
||||
// IMPORTANT: Each skill can only be acquired ONCE in a person's lifetime
|
||||
// Skip if user already has this skill
|
||||
if (in_array($skillName, $userSkillsAcquired)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Mark this skill as acquired
|
||||
$userSkillsAcquired[] = $skillName;
|
||||
|
||||
$skillStartDate = $packageStartDate->copy()->addDays(rand(0, 30));
|
||||
|
||||
// Skill is acquired once and stays forever (no end date)
|
||||
// The person has this skill for life once they learn it
|
||||
$skillEndDate = null;
|
||||
|
||||
// Calculate duration from start to now
|
||||
$durationMonths = $skillStartDate->diffInMonths(Carbon::now());
|
||||
$durationMonths = max(1, $durationMonths);
|
||||
|
||||
// Calculate proficiency level based on duration
|
||||
$proficiencyIndex = min(3, max(0, floor($durationMonths / 6)));
|
||||
$proficiencyLevel = ['beginner', 'intermediate', 'advanced', 'expert'][$proficiencyIndex];
|
||||
|
||||
SkillAcquisition::create([
|
||||
'club_affiliation_id' => $affiliation->id,
|
||||
'package_id' => $package->id,
|
||||
'activity_id' => $activity->id,
|
||||
'instructor_id' => $instructor->id,
|
||||
'skill_name' => $skillName,
|
||||
'icon' => 'bi-star',
|
||||
'duration_months' => $durationMonths,
|
||||
'start_date' => $skillStartDate,
|
||||
'end_date' => $skillEndDate,
|
||||
'proficiency_level' => $proficiencyLevel,
|
||||
'notes' => 'Skill acquired at ' . $clubData['name'],
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add some affiliation media (certificates, photos)
|
||||
$mediaCount = rand(1, 3);
|
||||
for ($m = 0; $m < $mediaCount; $m++) {
|
||||
AffiliationMedia::create([
|
||||
'club_affiliation_id' => $affiliation->id,
|
||||
'media_type' => ['certificate', 'photo', 'document'][rand(0, 2)],
|
||||
'title' => ['Membership Certificate', 'Training Photo', 'Achievement Award'][rand(0, 2)],
|
||||
'media_url' => 'affiliations/sample_' . rand(1, 100) . '.jpg',
|
||||
'description' => 'Sample media file',
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
// No cross-club skill progression needed
|
||||
// Each skill is acquired once and belongs to the person for life
|
||||
}
|
||||
|
||||
$this->command->info('✓ Affiliations data seeded successfully!');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
namespace Database\Seeders;
|
||||
|
||||
use Illuminate\Database\Seeder;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
class CleanAndReseedAffiliations extends Seeder
|
||||
{
|
||||
/**
|
||||
* Run the database seeds.
|
||||
*/
|
||||
public function run(): void
|
||||
{
|
||||
$this->command->info('Cleaning old affiliation data...');
|
||||
|
||||
// Delete in correct order due to foreign keys
|
||||
DB::table('skill_acquisitions')->delete();
|
||||
DB::table('affiliation_media')->delete();
|
||||
DB::table('club_member_subscriptions')->delete();
|
||||
DB::table('club_package_activities')->delete();
|
||||
DB::table('club_affiliations')->delete();
|
||||
DB::table('club_packages')->delete();
|
||||
DB::table('club_activities')->delete();
|
||||
|
||||
$this->command->info('✓ Old data cleaned successfully!');
|
||||
$this->command->info('');
|
||||
|
||||
// Now run the affiliations seeder
|
||||
$this->call(AffiliationsDataSeeder::class);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user