Commit 0efcf003 authored by Mahmoud Aglan's avatar Mahmoud Aglan

Fix HR seeder: create branches/people/trainers from scratch, idempotent cleanup

Production had no trainers/employees and only 1 branch. Rewrote seeder to be
fully self-contained: creates 2 new branches, 19 people, 11 employees, 12 trainers,
then populates all HR tables. Cleans up prior data on re-run.
Co-Authored-By: 's avatarClaude Opus 4.6 <noreply@anthropic.com>
parent 0184eb2d
...@@ -11,10 +11,22 @@ class HRDemoSeeder extends Seeder ...@@ -11,10 +11,22 @@ class HRDemoSeeder extends Seeder
public function run(): void public function run(): void
{ {
$academyId = 1; $academyId = 1;
$userId = 1; // admin user $userId = 1;
$now = now(); $now = now();
// Enable payroll // Clean up any orphan data from previous failed runs
DB::table('trainer_rate_history')->where('academy_id', $academyId)->delete();
DB::table('trainer_advances')->where('academy_id', $academyId)->delete();
DB::table('payslip_items')->whereIn('payslip_id', DB::table('payslips')->where('academy_id', $academyId)->pluck('id'))->delete();
DB::table('payslips')->where('academy_id', $academyId)->delete();
DB::table('payroll_periods')->where('academy_id', $academyId)->delete();
DB::table('trainer_compensations')->where('academy_id', $academyId)->delete();
DB::table('trainer_availability')->whereIn('trainer_id', DB::table('trainers')->where('academy_id', $academyId)->pluck('id'))->delete();
DB::table('trainer_qualifications')->whereIn('trainer_id', DB::table('trainers')->where('academy_id', $academyId)->pluck('id'))->delete();
DB::table('trainers')->where('academy_id', $academyId)->delete();
DB::table('employees')->where('academy_id', $academyId)->delete();
// ─── Enable payroll settings ───
DB::table('system_settings')->updateOrInsert( DB::table('system_settings')->updateOrInsert(
['academy_id' => $academyId, 'key' => 'payroll_enabled'], ['academy_id' => $academyId, 'key' => 'payroll_enabled'],
['group' => 'hr', 'value' => '1', 'type' => 'boolean', 'description_ar' => 'تفعيل نظام الرواتب', 'is_public' => false, 'created_at' => $now, 'updated_at' => $now] ['group' => 'hr', 'value' => '1', 'type' => 'boolean', 'description_ar' => 'تفعيل نظام الرواتب', 'is_public' => false, 'created_at' => $now, 'updated_at' => $now]
...@@ -24,47 +36,137 @@ public function run(): void ...@@ -24,47 +36,137 @@ public function run(): void
['group' => 'hr', 'value' => '1', 'type' => 'boolean', 'description_ar' => 'تفعيل حساب مستحقات المدربين تلقائياً', 'is_public' => false, 'created_at' => $now, 'updated_at' => $now] ['group' => 'hr', 'value' => '1', 'type' => 'boolean', 'description_ar' => 'تفعيل حساب مستحقات المدربين تلقائياً', 'is_public' => false, 'created_at' => $now, 'updated_at' => $now]
); );
// ─── Create new People for employees (non-trainer staff) ─── // ─── Create branches if missing ───
$staffPeople = [ $branch1 = DB::table('branches')->where('academy_id', $academyId)->where('name_ar', 'المعادي')->first();
['name_ar' => 'أحمد وجدي', 'name' => 'Ahmed Wagdy', 'phone' => '01055000001', 'gender' => 'male', 'date_of_birth' => '1985-03-12'], if (!$branch1) {
['name_ar' => 'سمر حسني', 'name' => 'Samar Hosny', 'phone' => '01055000002', 'gender' => 'female', 'date_of_birth' => '1990-07-22'], $branch1Id = DB::table('branches')->insertGetId([
['name_ar' => 'محمود عادل', 'name' => 'Mahmoud Adel', 'phone' => '01055000003', 'gender' => 'male', 'date_of_birth' => '1988-11-05'], 'uuid' => Str::uuid(),
['name_ar' => 'هند السعيد', 'name' => 'Hend Elsaeed', 'phone' => '01055000004', 'gender' => 'female', 'date_of_birth' => '1992-01-19'], 'academy_id' => $academyId,
['name_ar' => 'عبدالرحمن فوزي', 'name' => 'Abdelrahman F.', 'phone' => '01055000005', 'gender' => 'male', 'date_of_birth' => '1995-06-30'], 'name' => 'Maadi',
['name_ar' => 'نورهان ياسر', 'name' => 'Nourhan Yasser', 'phone' => '01055000006', 'gender' => 'female', 'date_of_birth' => '1993-09-14'], 'name_ar' => 'المعادي',
]; 'code' => 'MAA',
'phone' => '0225123456',
'address' => 'شارع 9 — المعادي',
'city' => 'القاهرة',
'governorate' => 'القاهرة',
'is_active' => true,
'created_at' => $now,
'updated_at' => $now,
]);
} else {
$branch1Id = $branch1->id;
}
$staffPersonIds = []; $branch2 = DB::table('branches')->where('academy_id', $academyId)->where('name_ar', 'مصر الجديدة')->first();
foreach ($staffPeople as $p) { if (!$branch2) {
$staffPersonIds[] = DB::table('people')->insertGetId([ $branch2Id = DB::table('branches')->insertGetId([
'uuid' => Str::uuid(), 'uuid' => Str::uuid(),
'academy_id' => $academyId, 'academy_id' => $academyId,
'name' => $p['name'], 'name' => 'Heliopolis',
'name_ar' => $p['name_ar'], 'name_ar' => 'مصر الجديدة',
'phone' => $p['phone'], 'code' => 'HEL',
'gender' => $p['gender'], 'phone' => '0224567890',
'date_of_birth' => $p['date_of_birth'], 'address' => 'شارع الميرغني — مصر الجديدة',
'nationality' => 'Egyptian', 'city' => 'القاهرة',
'created_by' => $userId, 'governorate' => 'القاهرة',
'is_active' => true,
'created_at' => $now, 'created_at' => $now,
'updated_at' => $now, 'updated_at' => $now,
]); ]);
} else {
$branch2Id = $branch2->id;
}
// Use existing branch 3 (Sheikh Zayed)
$branch3Id = 3;
$branchIds = [$branch1Id, $branch2Id, $branch3Id];
// ─── Create People for staff (non-trainers) ───
$staffData = [
['name_ar' => 'أحمد وجدي حسن', 'name' => 'Ahmed Wagdy', 'phone' => '01055000001', 'gender' => 'male', 'dob' => '1985-03-12'],
['name_ar' => 'سمر حسني إبراهيم', 'name' => 'Samar Hosny', 'phone' => '01055000002', 'gender' => 'female', 'dob' => '1990-07-22'],
['name_ar' => 'محمود عادل سيد', 'name' => 'Mahmoud Adel', 'phone' => '01055000003', 'gender' => 'male', 'dob' => '1988-11-05'],
['name_ar' => 'هند السعيد محمد', 'name' => 'Hend Elsaeed', 'phone' => '01055000004', 'gender' => 'female', 'dob' => '1992-01-19'],
['name_ar' => 'عبدالرحمن فوزي خالد', 'name' => 'Abdelrahman F.', 'phone' => '01055000005', 'gender' => 'male', 'dob' => '1995-06-30'],
['name_ar' => 'نورهان ياسر أحمد', 'name' => 'Nourhan Yasser', 'phone' => '01055000006', 'gender' => 'female', 'dob' => '1993-09-14'],
['name_ar' => 'طارق حسان محمود', 'name' => 'Tarek Hassan', 'phone' => '01055000099', 'gender' => 'male', 'dob' => '1991-04-11'],
];
$staffPersonIds = [];
foreach ($staffData as $s) {
$existing = DB::table('people')->where('academy_id', $academyId)->where('phone', $s['phone'])->first();
if ($existing) {
$staffPersonIds[] = $existing->id;
} else {
$staffPersonIds[] = DB::table('people')->insertGetId([
'uuid' => Str::uuid(),
'academy_id' => $academyId,
'name' => $s['name'],
'name_ar' => $s['name_ar'],
'phone' => $s['phone'],
'gender' => $s['gender'],
'date_of_birth' => $s['dob'],
'nationality' => 'Egyptian',
'created_by' => $userId,
'created_at' => $now,
'updated_at' => $now,
]);
}
} }
// ─── Create Employees (6 staff + 4 trainers as employees) ─── // ─── Create People for trainers ───
$trainerPeople = [
['name_ar' => 'كريم عبدالله حسن', 'name' => 'Karim Abdullah', 'phone' => '01099000001', 'gender' => 'male', 'dob' => '1990-05-20'],
['name_ar' => 'أحمد سمير فتحي', 'name' => 'Ahmed Samir', 'phone' => '01099000002', 'gender' => 'male', 'dob' => '1988-08-15'],
['name_ar' => 'دينا محمد علي', 'name' => 'Dina Mohamed', 'phone' => '01099000003', 'gender' => 'female', 'dob' => '1992-03-10'],
['name_ar' => 'طارق حسام الدين', 'name' => 'Tarek Hossam', 'phone' => '01099000004', 'gender' => 'male', 'dob' => '1985-12-01'],
['name_ar' => 'مريم خالد إبراهيم', 'name' => 'Mariam Khaled', 'phone' => '01099000005', 'gender' => 'female', 'dob' => '1994-07-25'],
['name_ar' => 'حسام الدين محمود', 'name' => 'Hossam Mahmoud', 'phone' => '01099000006', 'gender' => 'male', 'dob' => '1983-02-14'],
['name_ar' => 'رانيا سعيد أحمد', 'name' => 'Rania Saeed', 'phone' => '01099000007', 'gender' => 'female', 'dob' => '1991-11-30'],
['name_ar' => 'عمر حسن محمد', 'name' => 'Omar Hassan', 'phone' => '01099000008', 'gender' => 'male', 'dob' => '1989-06-18'],
['name_ar' => 'سلمى يوسف علي', 'name' => 'Salma Youssef', 'phone' => '01099000009', 'gender' => 'female', 'dob' => '1995-09-05'],
['name_ar' => 'يوسف عماد حسين', 'name' => 'Youssef Emad', 'phone' => '01099000010', 'gender' => 'male', 'dob' => '1993-01-22'],
['name_ar' => 'ليلى أسامة فتحي', 'name' => 'Laila Osama', 'phone' => '01099000011', 'gender' => 'female', 'dob' => '1996-04-16'],
['name_ar' => 'كريم وليد سامي', 'name' => 'Karim Waleed', 'phone' => '01099000012', 'gender' => 'male', 'dob' => '1991-10-08'],
];
$trainerPersonIds = [];
foreach ($trainerPeople as $t) {
$existing = DB::table('people')->where('academy_id', $academyId)->where('phone', $t['phone'])->first();
if ($existing) {
$trainerPersonIds[] = $existing->id;
} else {
$trainerPersonIds[] = DB::table('people')->insertGetId([
'uuid' => Str::uuid(),
'academy_id' => $academyId,
'name' => $t['name'],
'name_ar' => $t['name_ar'],
'phone' => $t['phone'],
'gender' => $t['gender'],
'date_of_birth' => $t['dob'],
'nationality' => 'Egyptian',
'created_by' => $userId,
'created_at' => $now,
'updated_at' => $now,
]);
}
}
// ─── Create Employees ───
// 6 staff employees + 4 trainer-employees = 10 active + 1 resigned
$employees = [ $employees = [
// Non-trainer staff ['person_id' => $staffPersonIds[0], 'number' => 'EMP-001', 'dept' => 'management', 'position' => 'مدير الأكاديمية', 'type' => 'full_time', 'salary' => 2500000, 'branch' => $branch1Id, 'start' => '2023-01-15'],
['person_id' => $staffPersonIds[0], 'number' => 'EMP-001', 'dept' => 'management', 'position' => 'مدير الأكاديمية', 'type' => 'full_time', 'salary' => 2500000, 'branch_id' => 1, 'start' => '2023-01-15'], ['person_id' => $staffPersonIds[1], 'number' => 'EMP-002', 'dept' => 'reception', 'position' => 'موظفة استقبال', 'type' => 'full_time', 'salary' => 800000, 'branch' => $branch1Id, 'start' => '2023-03-01'],
['person_id' => $staffPersonIds[1], 'number' => 'EMP-002', 'dept' => 'reception', 'position' => 'موظفة استقبال', 'type' => 'full_time', 'salary' => 800000, 'branch_id' => 1, 'start' => '2023-03-01'], ['person_id' => $staffPersonIds[2], 'number' => 'EMP-003', 'dept' => 'operations', 'position' => 'مشرف العمليات', 'type' => 'full_time', 'salary' => 1500000, 'branch' => $branch2Id, 'start' => '2023-02-10'],
['person_id' => $staffPersonIds[2], 'number' => 'EMP-003', 'dept' => 'operations', 'position' => 'مشرف العمليات', 'type' => 'full_time', 'salary' => 1500000, 'branch_id' => 2, 'start' => '2023-02-10'], ['person_id' => $staffPersonIds[3], 'number' => 'EMP-004', 'dept' => 'reception', 'position' => 'موظفة استقبال', 'type' => 'part_time', 'salary' => 500000, 'branch' => $branch2Id, 'start' => '2023-06-01'],
['person_id' => $staffPersonIds[3], 'number' => 'EMP-004', 'dept' => 'reception', 'position' => 'موظفة استقبال', 'type' => 'part_time', 'salary' => 500000, 'branch_id' => 2, 'start' => '2023-06-01'], ['person_id' => $staffPersonIds[4], 'number' => 'EMP-005', 'dept' => 'maintenance', 'position' => 'فني صيانة', 'type' => 'full_time', 'salary' => 700000, 'branch' => $branch3Id, 'start' => '2023-09-15'],
['person_id' => $staffPersonIds[4], 'number' => 'EMP-005', 'dept' => 'maintenance', 'position' => 'فني صيانة', 'type' => 'full_time', 'salary' => 700000, 'branch_id' => 3, 'start' => '2023-09-15'], ['person_id' => $staffPersonIds[5], 'number' => 'EMP-006', 'dept' => 'accounting', 'position' => 'محاسبة', 'type' => 'full_time', 'salary' => 1200000, 'branch' => $branch1Id, 'start' => '2024-01-01'],
['person_id' => $staffPersonIds[5], 'number' => 'EMP-006', 'dept' => 'accounting', 'position' => 'محاسبة', 'type' => 'full_time', 'salary' => 1200000, 'branch_id' => 1, 'start' => '2024-01-01'], // Trainer-employees (salaried trainers)
// Trainers who are also employees (salaried trainers) ['person_id' => $trainerPersonIds[0], 'number' => 'EMP-007', 'dept' => 'training', 'position' => 'مدرب أول', 'type' => 'full_time', 'salary' => 1800000, 'branch' => $branch1Id, 'start' => '2023-01-15'],
['person_id' => 4, 'number' => 'EMP-007', 'dept' => 'training', 'position' => 'مدرب أول', 'type' => 'full_time', 'salary' => 1800000, 'branch_id' => 1, 'start' => '2023-01-15'], ['person_id' => $trainerPersonIds[1], 'number' => 'EMP-008', 'dept' => 'training', 'position' => 'مدرب أول', 'type' => 'full_time', 'salary' => 1600000, 'branch' => $branch1Id, 'start' => '2023-02-01'],
['person_id' => 5, 'number' => 'EMP-008', 'dept' => 'training', 'position' => 'مدرب أول', 'type' => 'full_time', 'salary' => 1600000, 'branch_id' => 1, 'start' => '2023-02-01'], ['person_id' => $trainerPersonIds[5], 'number' => 'EMP-009', 'dept' => 'training', 'position' => 'رئيس مدربين', 'type' => 'full_time', 'salary' => 2200000, 'branch' => $branch2Id, 'start' => '2022-11-01'],
['person_id' => 9, 'number' => 'EMP-009', 'dept' => 'training', 'position' => 'رئيس مدربين', 'type' => 'full_time', 'salary' => 2200000, 'branch_id' => 2, 'start' => '2022-11-01'], ['person_id' => $trainerPersonIds[9], 'number' => 'EMP-010', 'dept' => 'training', 'position' => 'مدرب', 'type' => 'contract', 'salary' => 1000000, 'branch' => $branch3Id, 'start' => '2024-03-01'],
['person_id' => 13, 'number' => 'EMP-010', 'dept' => 'training', 'position' => 'مدرب', 'type' => 'contract', 'salary' => 1000000, 'branch_id' => 3, 'start' => '2024-03-01'],
]; ];
$employeeIds = []; $employeeIds = [];
...@@ -82,7 +184,7 @@ public function run(): void ...@@ -82,7 +184,7 @@ public function run(): void
'salary_amount' => $e['salary'], 'salary_amount' => $e['salary'],
'salary_frequency' => 'monthly', 'salary_frequency' => 'monthly',
'working_hours_per_week' => $e['type'] === 'part_time' ? 20.0 : 40.0, 'working_hours_per_week' => $e['type'] === 'part_time' ? 20.0 : 40.0,
'branch_id' => $e['branch_id'], 'branch_id' => $e['branch'],
'manager_id' => $i > 0 ? $managerId : null, 'manager_id' => $i > 0 ? $managerId : null,
'status' => 'active', 'status' => 'active',
'created_by' => $userId, 'created_by' => $userId,
...@@ -93,24 +195,11 @@ public function run(): void ...@@ -93,24 +195,11 @@ public function run(): void
if ($i === 0) $managerId = $id; if ($i === 0) $managerId = $id;
} }
// Add one resigned employee // Add resigned employee
$resignedPerson = DB::table('people')->insertGetId([
'uuid' => Str::uuid(),
'academy_id' => $academyId,
'name' => 'Tarek Hassan',
'name_ar' => 'طارق حسان',
'phone' => '01055000099',
'gender' => 'male',
'date_of_birth' => '1991-04-11',
'nationality' => 'Egyptian',
'created_by' => $userId,
'created_at' => $now,
'updated_at' => $now,
]);
DB::table('employees')->insert([ DB::table('employees')->insert([
'uuid' => Str::uuid(), 'uuid' => Str::uuid(),
'academy_id' => $academyId, 'academy_id' => $academyId,
'person_id' => $resignedPerson, 'person_id' => $staffPersonIds[6],
'employee_number' => 'EMP-011', 'employee_number' => 'EMP-011',
'department' => 'training', 'department' => 'training',
'position' => 'مدرب سباحة', 'position' => 'مدرب سباحة',
...@@ -120,7 +209,7 @@ public function run(): void ...@@ -120,7 +209,7 @@ public function run(): void
'salary_amount' => 900000, 'salary_amount' => 900000,
'salary_frequency' => 'monthly', 'salary_frequency' => 'monthly',
'working_hours_per_week' => 40.0, 'working_hours_per_week' => 40.0,
'branch_id' => 2, 'branch_id' => $branch2Id,
'manager_id' => $managerId, 'manager_id' => $managerId,
'status' => 'resigned', 'status' => 'resigned',
'termination_reason' => 'انتقل لأكاديمية أخرى', 'termination_reason' => 'انتقل لأكاديمية أخرى',
...@@ -129,130 +218,126 @@ public function run(): void ...@@ -129,130 +218,126 @@ public function run(): void
'updated_at' => $now, 'updated_at' => $now,
]); ]);
// One on-leave employee // Set one on-leave
DB::table('employees')->where('id', $employeeIds['EMP-004'])->update([ DB::table('employees')->where('id', $employeeIds['EMP-004'])->update([
'status' => 'on_leave', 'status' => 'on_leave',
'notes' => 'إجازة أمومة — العودة المتوقعة 2026-09-01', 'notes' => 'إجازة أمومة — العودة المتوقعة 2026-09-01',
]); ]);
// ─── Link trainers 1,2,6,10 to their employee records ─── // ─── Create Trainers ───
// Trainer 1 (person_id=4) → EMP-007, change to hybrid // 4 employee-based + 8 freelancers = 12 total
DB::table('trainers')->where('id', 1)->update([ $trainerDefs = [
'employee_id' => $employeeIds['EMP-007'], // Employee-based trainers (indices 0, 1, 5, 9 of trainerPersonIds → linked to EMP-007..010)
'compensation_model' => 'hybrid', ['idx' => 0, 'emp' => 'EMP-007', 'num' => 'TR-001', 'model' => 'hybrid', 'session' => 15000, 'hourly' => null, 'group' => null, 'player' => null, 'rev' => null],
'session_rate' => 15000, ['idx' => 1, 'emp' => 'EMP-008', 'num' => 'TR-002', 'model' => 'salary', 'session' => null, 'hourly' => null, 'group' => null, 'player' => null, 'rev' => null],
'hourly_rate' => null, ['idx' => 5, 'emp' => 'EMP-009', 'num' => 'TR-003', 'model' => 'hybrid', 'session' => null, 'hourly' => null, 'group' => 25000, 'player' => null, 'rev' => null],
]); ['idx' => 9, 'emp' => 'EMP-010', 'num' => 'TR-004', 'model' => 'per_group', 'session' => null, 'hourly' => null, 'group' => 20000, 'player' => null, 'rev' => null],
// Trainer 2 (person_id=5) → EMP-008, salary model // Freelancer trainers (indices 2,3,4,6,7,8,10,11)
DB::table('trainers')->where('id', 2)->update([ ['idx' => 2, 'emp' => null, 'num' => 'TR-005', 'model' => 'per_session', 'session' => 12000, 'hourly' => 15000, 'group' => null, 'player' => null, 'rev' => null],
'employee_id' => $employeeIds['EMP-008'], ['idx' => 3, 'emp' => null, 'num' => 'TR-006', 'model' => 'per_session', 'session' => 18000, 'hourly' => null, 'group' => null, 'player' => null, 'rev' => null],
'compensation_model' => 'salary', ['idx' => 4, 'emp' => null, 'num' => 'TR-007', 'model' => 'per_session', 'session' => 14000, 'hourly' => null, 'group' => null, 'player' => null, 'rev' => null],
]); ['idx' => 6, 'emp' => null, 'num' => 'TR-008', 'model' => 'per_session', 'session' => 16000, 'hourly' => null, 'group' => null, 'player' => null, 'rev' => null],
// Trainer 6 (person_id=9) → EMP-009, hybrid model (head trainer) ['idx' => 7, 'emp' => null, 'num' => 'TR-009', 'model' => 'hourly', 'session' => null, 'hourly' => 13000, 'group' => null, 'player' => null, 'rev' => null],
DB::table('trainers')->where('id', 6)->update([ ['idx' => 8, 'emp' => null, 'num' => 'TR-010', 'model' => 'per_session', 'session' => 11000, 'hourly' => null, 'group' => null, 'player' => null, 'rev' => null],
'employee_id' => $employeeIds['EMP-009'], ['idx' => 10, 'emp' => null, 'num' => 'TR-011', 'model' => 'hourly', 'session' => null, 'hourly' => 14000, 'group' => null, 'player' => null, 'rev' => null],
'compensation_model' => 'hybrid', ['idx' => 11, 'emp' => null, 'num' => 'TR-012', 'model' => 'per_player', 'session' => 17000, 'hourly' => null, 'group' => null, 'player' => 5000, 'rev' => null],
'group_rate' => 25000, ];
]);
// Trainer 10 (person_id=13) → EMP-010, per_group model
DB::table('trainers')->where('id', 10)->update([
'employee_id' => $employeeIds['EMP-010'],
'compensation_model' => 'per_group',
'group_rate' => 20000,
]);
// Update remaining freelancer trainers with proper rates $trainerBios = [
DB::table('trainers')->where('id', 3)->update(['session_rate' => 12000, 'hourly_rate' => 15000]); // Dina 0 => ['bio_ar' => 'مدرب كرة قدم محترف — خبرة 10 سنوات', 'sports' => '["football"]', 'spec' => '["youth","tactics"]'],
DB::table('trainers')->where('id', 4)->update(['session_rate' => 18000]); // Tarek 1 => ['bio_ar' => 'مدرب لياقة بدنية معتمد — AFC Level 2', 'sports' => '["fitness"]', 'spec' => '["strength","cardio"]'],
DB::table('trainers')->where('id', 5)->update(['session_rate' => 14000]); // Mariam 2 => ['bio_ar' => 'مدربة جمباز — بطلة مصر سابقة', 'sports' => '["gymnastics"]', 'spec' => '["artistic","rhythm"]'],
DB::table('trainers')->where('id', 7)->update(['session_rate' => 16000]); // Rania 3 => ['bio_ar' => 'مدرب سباحة — خبرة أكاديمية وتنافسية', 'sports' => '["swimming"]', 'spec' => '["freestyle","butterfly"]'],
DB::table('trainers')->where('id', 8)->update(['hourly_rate' => 13000]); // Omar 4 => ['bio_ar' => 'مدربة كاراتيه — حزام أسود دان 3', 'sports' => '["karate"]', 'spec' => '["kata","kumite"]'],
DB::table('trainers')->where('id', 9)->update(['session_rate' => 11000]); // Salma 5 => ['bio_ar' => 'رئيس المدربين — مدرب متعدد الرياضات', 'sports' => '["football","fitness"]', 'spec' => '["management","planning"]'],
DB::table('trainers')->where('id', 11)->update(['hourly_rate' => 14000]); // Laila 6 => ['bio_ar' => 'مدربة باليه وجمباز إيقاعي', 'sports' => '["ballet","gymnastics"]','spec' => '["ballet","flexibility"]'],
DB::table('trainers')->where('id', 12)->update(['session_rate' => 17000, 'compensation_model' => 'per_player', 'player_rate' => 5000]); // Karim 7 => ['bio_ar' => 'مدرب كرة سلة — الدوري الممتاز سابقاً', 'sports' => '["basketball"]', 'spec' => '["shooting","defense"]'],
8 => ['bio_ar' => 'مدربة يوغا وبيلاتس', 'sports' => '["yoga","pilates"]', 'spec' => '["vinyasa","rehabilitation"]'],
// Update all trainers with bios and sports 9 => ['bio_ar' => 'مدرب كيك بوكسينج ولياقة قتالية', 'sports' => '["kickboxing"]', 'spec' => '["combat_fitness","self_defense"]'],
$trainerMeta = [ 10 => ['bio_ar' => 'مدربة سباحة أطفال — متخصصة في الأعمار الصغيرة', 'sports' => '["swimming"]', 'spec' => '["kids","water_safety"]'],
1 => ['bio_ar' => 'مدرب كرة قدم محترف — خبرة 10 سنوات', 'sports' => '["football"]', 'specializations' => '["youth","tactics"]'], 11 => ['bio_ar' => 'مدرب تنس — بطل الجمهورية تحت 18', 'sports' => '["tennis"]', 'spec' => '["singles","juniors"]'],
2 => ['bio_ar' => 'مدرب لياقة بدنية معتمد — AFC Level 2', 'sports' => '["fitness"]', 'specializations' => '["strength","cardio"]'],
3 => ['bio_ar' => 'مدربة جمباز — بطلة مصر سابقة', 'sports' => '["gymnastics"]', 'specializations' => '["artistic","rhythm"]'],
4 => ['bio_ar' => 'مدرب سباحة — خبرة أكاديمية وتنافسية', 'sports' => '["swimming"]', 'specializations' => '["freestyle","butterfly"]'],
5 => ['bio_ar' => 'مدربة كاراتيه — حزام أسود دان 3', 'sports' => '["karate"]', 'specializations' => '["kata","kumite"]'],
6 => ['bio_ar' => 'رئيس المدربين — مدرب متعدد الرياضات', 'sports' => '["football","fitness"]', 'specializations' => '["management","planning"]'],
7 => ['bio_ar' => 'مدربة باليه وجمباز إيقاعي', 'sports' => '["ballet","gymnastics"]', 'specializations' => '["ballet","flexibility"]'],
8 => ['bio_ar' => 'مدرب كرة سلة — الدوري الممتاز سابقاً', 'sports' => '["basketball"]', 'specializations' => '["shooting","defense"]'],
9 => ['bio_ar' => 'مدربة يوغا وبيلاتس', 'sports' => '["yoga","pilates"]', 'specializations' => '["vinyasa","rehabilitation"]'],
10 => ['bio_ar' => 'مدرب كيك بوكسينج ولياقة قتالية', 'sports' => '["kickboxing"]', 'specializations' => '["combat_fitness","self_defense"]'],
11 => ['bio_ar' => 'مدربة سباحة أطفال — متخصصة في الأعمار الصغيرة', 'sports' => '["swimming"]', 'specializations' => '["kids","water_safety"]'],
12 => ['bio_ar' => 'مدرب تنس — بطل الجمهورية تحت 18', 'sports' => '["tennis"]', 'specializations' => '["singles","juniors"]'],
]; ];
foreach ($trainerMeta as $tId => $meta) {
DB::table('trainers')->where('id', $tId)->update([ $trainerIds = [];
'bio_ar' => $meta['bio_ar'], foreach ($trainerDefs as $i => $td) {
'sports' => $meta['sports'], $bio = $trainerBios[$i];
'specializations' => $meta['specializations'], $trainerIds[$i] = DB::table('trainers')->insertGetId([
'total_sessions_conducted' => rand(40, 200), 'uuid' => Str::uuid(),
'academy_id' => $academyId,
'employee_id' => $td['emp'] ? $employeeIds[$td['emp']] : null,
'person_id' => $trainerPersonIds[$td['idx']],
'trainer_number' => $td['num'],
'bio_ar' => $bio['bio_ar'],
'sports' => $bio['sports'],
'specializations' => $bio['spec'],
'compensation_model' => $td['model'],
'session_rate' => $td['session'],
'hourly_rate' => $td['hourly'],
'group_rate' => $td['group'],
'player_rate' => $td['player'],
'revenue_share_percent' => $td['rev'],
'rating' => rand(35, 49) / 10, 'rating' => rand(35, 49) / 10,
'total_sessions_conducted' => rand(40, 200),
'max_daily_sessions' => rand(3, 6), 'max_daily_sessions' => rand(3, 6),
'max_weekly_hours' => rand(20, 35), 'max_weekly_hours' => rand(20, 35),
'status' => 'active',
'created_at' => $now,
'updated_at' => $now,
]); ]);
} }
// ─── Trainer Qualifications ─── // ─── Trainer Qualifications ───
$qualifications = [ $qualifications = [
['trainer_id' => 1, 'name' => 'AFC C License', 'issuer' => 'اتحاد الكرة المصري', 'issue_date' => '2020-06-15', 'expiry_date' => '2027-06-15', 'is_verified' => true], [0, 'AFC C License', 'اتحاد الكرة المصري', '2020-06-15', '2027-06-15', true],
['trainer_id' => 1, 'name' => 'First Aid CPR', 'issuer' => 'الهلال الأحمر', 'issue_date' => '2024-01-10', 'expiry_date' => '2026-01-10', 'is_verified' => true], [0, 'First Aid CPR', 'الهلال الأحمر', '2024-01-10', '2026-01-10', true],
['trainer_id' => 2, 'name' => 'NASM CPT', 'issuer' => 'NASM', 'issue_date' => '2019-03-20', 'expiry_date' => '2027-03-20', 'is_verified' => true], [1, 'NASM CPT', 'NASM', '2019-03-20', '2027-03-20', true],
['trainer_id' => 3, 'name' => 'شهادة تدريب جمباز', 'issuer' => 'الاتحاد المصري للجمباز', 'issue_date' => '2021-09-01', 'expiry_date' => null, 'is_verified' => true], [2, 'شهادة تدريب جمباز', 'الاتحاد المصري للجمباز', '2021-09-01', null, true],
['trainer_id' => 4, 'name' => 'FINA Swimming Coach', 'issuer' => 'FINA', 'issue_date' => '2022-05-15', 'expiry_date' => '2026-05-15', 'is_verified' => true], [3, 'FINA Swimming Coach', 'FINA', '2022-05-15', '2026-05-15', true],
['trainer_id' => 5, 'name' => 'حزام أسود دان 3', 'issuer' => 'الاتحاد العالمي للكاراتيه', 'issue_date' => '2018-11-20', 'expiry_date' => null, 'is_verified' => true], [4, 'حزام أسود دان 3', 'الاتحاد العالمي للكاراتيه', '2018-11-20', null, true],
['trainer_id' => 6, 'name' => 'AFC B License', 'issuer' => 'الاتحاد الآسيوي', 'issue_date' => '2019-08-01', 'expiry_date' => '2027-08-01', 'is_verified' => true], [5, 'AFC B License', 'الاتحاد الآسيوي', '2019-08-01', '2027-08-01', true],
['trainer_id' => 6, 'name' => 'NSCA CSCS', 'issuer' => 'NSCA', 'issue_date' => '2020-02-15', 'expiry_date' => '2026-02-15', 'is_verified' => true], [5, 'NSCA CSCS', 'NSCA', '2020-02-15', '2026-02-15', true],
['trainer_id' => 7, 'name' => 'RAD Ballet Teacher', 'issuer' => 'Royal Academy of Dance', 'issue_date' => '2021-03-10', 'expiry_date' => null, 'is_verified' => true], [6, 'RAD Ballet Teacher', 'Royal Academy of Dance', '2021-03-10', null, true],
['trainer_id' => 8, 'name' => 'FIBA Coaching License','issuer' => 'FIBA', 'issue_date' => '2020-10-01', 'expiry_date' => '2026-10-01', 'is_verified' => false], [7, 'FIBA Coaching License','FIBA', '2020-10-01', '2026-10-01', false],
['trainer_id' => 11, 'name' => 'STA Baby Swimming', 'issuer' => 'STA', 'issue_date' => '2023-07-01', 'expiry_date' => '2025-07-01', 'is_verified' => true], [10,'STA Baby Swimming', 'STA', '2023-07-01', '2025-07-01', true],
['trainer_id' => 12, 'name' => 'ITF Coaching Level 2','issuer' => 'ITF', 'issue_date' => '2022-01-15', 'expiry_date' => '2027-01-15', 'is_verified' => true], [11,'ITF Coaching Level 2', 'ITF', '2022-01-15', '2027-01-15', true],
]; ];
foreach ($qualifications as $q) { foreach ($qualifications as [$idx, $name, $issuer, $issued, $expiry, $verified]) {
DB::table('trainer_qualifications')->insert([ DB::table('trainer_qualifications')->insert([
'trainer_id' => $q['trainer_id'], 'trainer_id' => $trainerIds[$idx],
'name' => $q['name'], 'name' => $name,
'issuer' => $q['issuer'], 'issuer' => $issuer,
'issue_date' => $q['issue_date'], 'issue_date' => $issued,
'expiry_date' => $q['expiry_date'], 'expiry_date' => $expiry,
'is_verified' => $q['is_verified'], 'is_verified' => $verified,
'verified_by' => $q['is_verified'] ? $userId : null, 'verified_by' => $verified ? $userId : null,
'verified_at' => $q['is_verified'] ? $now : null, 'verified_at' => $verified ? $now : null,
'created_at' => $now, 'created_at' => $now,
]); ]);
} }
// ─── Trainer Availability ─── // ─── Trainer Availability ───
foreach ([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12] as $tId) { foreach ($trainerIds as $i => $tId) {
// Available weekdays foreach ([0, 1, 2, 3, 4] as $day) {
foreach ([0, 1, 2, 3, 4] as $day) { // Sun-Thu
DB::table('trainer_availability')->insert([ DB::table('trainer_availability')->insert([
'trainer_id' => $tId, 'trainer_id' => $tId,
'type' => 'available', 'type' => 'available',
'day_of_week' => $day, 'day_of_week' => $day,
'start_time' => $tId <= 6 ? '08:00' : '14:00', 'start_time' => $i < 6 ? '08:00' : '14:00',
'end_time' => $tId <= 6 ? '16:00' : '22:00', 'end_time' => $i < 6 ? '16:00' : '22:00',
'created_at' => $now, 'created_at' => $now,
]); ]);
} }
// Some prefer specific times if ($i % 3 === 0) {
if ($tId % 3 === 0) {
DB::table('trainer_availability')->insert([ DB::table('trainer_availability')->insert([
'trainer_id' => $tId, 'trainer_id' => $tId,
'type' => 'preferred', 'type' => 'preferred',
'day_of_week' => 5, // Friday 'day_of_week' => 5,
'start_time' => '16:00', 'start_time' => '16:00',
'end_time' => '20:00', 'end_time' => '20:00',
'created_at' => $now, 'created_at' => $now,
]); ]);
} }
// Some unavailable specific dates if ($i % 4 === 0) {
if ($tId % 4 === 0) {
DB::table('trainer_availability')->insert([ DB::table('trainer_availability')->insert([
'trainer_id' => $tId, 'trainer_id' => $tId,
'type' => 'unavailable', 'type' => 'unavailable',
...@@ -265,17 +350,17 @@ public function run(): void ...@@ -265,17 +350,17 @@ public function run(): void
} }
} }
// ─── Trainer Compensations (last 3 months of history) ─── // ─── Trainer Compensations (3 months history) ───
$compensationData = []; $compensationData = [];
foreach ([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12] as $tId) { foreach ($trainerIds as $i => $tId) {
$trainer = DB::table('trainers')->where('id', $tId)->first(); $td = $trainerDefs[$i];
$model = $trainer->compensation_model; $model = $td['model'];
for ($monthsAgo = 2; $monthsAgo >= 0; $monthsAgo--) { for ($monthsAgo = 2; $monthsAgo >= 0; $monthsAgo--) {
$month = now()->subMonths($monthsAgo); $month = now()->subMonths($monthsAgo);
$sessionsThisMonth = rand(12, 24); $sessions = rand(12, 24);
for ($s = 0; $s < $sessionsThisMonth; $s++) { for ($s = 0; $s < $sessions; $s++) {
$day = rand(1, min(28, $month->daysInMonth)); $day = rand(1, min(28, $month->daysInMonth));
$date = $month->copy()->day($day)->toDateString(); $date = $month->copy()->day($day)->toDateString();
...@@ -288,15 +373,14 @@ public function run(): void ...@@ -288,15 +373,14 @@ public function run(): void
}; };
$rate = match ($model) { $rate = match ($model) {
'hourly' => $trainer->hourly_rate ?? 12000, 'hourly' => $td['hourly'] ?? 12000,
'per_group' => $trainer->group_rate ?? 20000, 'per_group' => $td['group'] ?? 20000,
'per_player' => $trainer->player_rate ?? 5000, 'per_player' => $td['player'] ?? 5000,
default => $trainer->session_rate ?? 15000, default => $td['session'] ?? 15000,
}; };
$quantity = $model === 'per_player' ? rand(5, 15) : 1; $quantity = $model === 'per_player' ? rand(5, 15) : 1;
$amount = (int) ($rate * $quantity); $amount = (int) ($rate * $quantity);
$status = $monthsAgo > 0 ? 'paid' : (rand(0, 3) === 0 ? 'pending' : 'approved'); $status = $monthsAgo > 0 ? 'paid' : (rand(0, 3) === 0 ? 'pending' : 'approved');
$compensationData[] = [ $compensationData[] = [
...@@ -318,8 +402,8 @@ public function run(): void ...@@ -318,8 +402,8 @@ public function run(): void
]; ];
} }
// Add a bonus for some trainers // Bonus for some
if ($tId % 3 === 1 && $monthsAgo === 1) { if ($i % 3 === 0 && $monthsAgo === 1) {
$compensationData[] = [ $compensationData[] = [
'uuid' => Str::uuid(), 'uuid' => Str::uuid(),
'academy_id' => $academyId, 'academy_id' => $academyId,
...@@ -339,8 +423,8 @@ public function run(): void ...@@ -339,8 +423,8 @@ public function run(): void
]; ];
} }
// Add a penalty for one trainer // Penalty for trainer 7 (basketball)
if ($tId === 8 && $monthsAgo === 0) { if ($i === 7 && $monthsAgo === 0) {
$compensationData[] = [ $compensationData[] = [
'uuid' => Str::uuid(), 'uuid' => Str::uuid(),
'academy_id' => $academyId, 'academy_id' => $academyId,
...@@ -362,19 +446,18 @@ public function run(): void ...@@ -362,19 +446,18 @@ public function run(): void
} }
} }
// Insert in chunks
foreach (array_chunk($compensationData, 100) as $chunk) { foreach (array_chunk($compensationData, 100) as $chunk) {
DB::table('trainer_compensations')->insert($chunk); DB::table('trainer_compensations')->insert($chunk);
} }
// ─── Payroll Periods (2 closed months + 1 open current) ─── // ─── Payroll Periods ───
$periods = []; $periods = [];
for ($m = 2; $m >= 0; $m--) { for ($m = 2; $m >= 0; $m--) {
$start = now()->subMonths($m)->startOfMonth()->toDateString(); $start = now()->subMonths($m)->startOfMonth()->toDateString();
$end = now()->subMonths($m)->endOfMonth()->toDateString(); $end = now()->subMonths($m)->endOfMonth()->toDateString();
$status = $m > 0 ? 'closed' : 'open'; $status = $m > 0 ? 'closed' : 'open';
$periodId = DB::table('payroll_periods')->insertGetId([ $periods[$m] = DB::table('payroll_periods')->insertGetId([
'uuid' => Str::uuid(), 'uuid' => Str::uuid(),
'academy_id' => $academyId, 'academy_id' => $academyId,
'period_start' => $start, 'period_start' => $start,
...@@ -391,52 +474,55 @@ public function run(): void ...@@ -391,52 +474,55 @@ public function run(): void
'created_at' => $now, 'created_at' => $now,
'updated_at' => $now, 'updated_at' => $now,
]); ]);
$periods[$m] = $periodId;
} }
// ─── Payslips for closed periods ─── // ─── Payslips for closed periods ───
$payslipNum = 1; $payslipNum = 1;
foreach ([2, 1] as $m) { foreach ([2, 1] as $m) {
$periodId = $periods[$m]; $periodId = $periods[$m];
$periodTotalGross = 0; $periodGross = 0;
$periodTotalDeductions = 0; $periodDed = 0;
$periodTotalNet = 0; $periodNet = 0;
$slipCount = 0; $slipCount = 0;
foreach ([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12] as $tId) { foreach ($trainerIds as $i => $tId) {
$trainer = DB::table('trainers')->where('id', $tId)->first(); $td = $trainerDefs[$i];
$employee = $trainer->employee_id ? DB::table('employees')->where('id', $trainer->employee_id)->first() : null; $empId = $td['emp'] ? $employeeIds[$td['emp']] : null;
$baseSalary = 0;
if ($empId) {
$emp = DB::table('employees')->where('id', $empId)->first();
$baseSalary = $emp->salary_amount ?? 0;
}
$baseSalary = $employee ? ($employee->salary_amount ?? 0) : 0;
$month = now()->subMonths($m); $month = now()->subMonths($m);
$monthStart = $month->copy()->startOfMonth()->toDateString();
$monthEnd = $month->copy()->endOfMonth()->toDateString();
// Sum session earnings for this trainer/month $sessionEarnings = (int) DB::table('trainer_compensations')
$sessionEarnings = DB::table('trainer_compensations')
->where('trainer_id', $tId) ->where('trainer_id', $tId)
->where('status', 'paid') ->where('status', 'paid')
->whereIn('type', ['session_pay', 'group_pay', 'player_pay', 'revenue_share', 'overtime', 'substitute']) ->whereIn('type', ['session_pay', 'group_pay', 'player_pay'])
->whereBetween('date', [$month->startOfMonth()->toDateString(), $month->endOfMonth()->toDateString()]) ->whereBetween('date', [$monthStart, $monthEnd])
->sum('amount'); ->sum('amount');
$bonuses = DB::table('trainer_compensations') $bonuses = (int) DB::table('trainer_compensations')
->where('trainer_id', $tId) ->where('trainer_id', $tId)->where('status', 'paid')
->where('status', 'paid')
->where('type', 'bonus') ->where('type', 'bonus')
->whereBetween('date', [$month->startOfMonth()->toDateString(), $month->endOfMonth()->toDateString()]) ->whereBetween('date', [$monthStart, $monthEnd])
->sum('amount'); ->sum('amount');
$penalties = DB::table('trainer_compensations') $penalties = (int) DB::table('trainer_compensations')
->where('trainer_id', $tId) ->where('trainer_id', $tId)
->where('status', '!=', 'cancelled') ->whereIn('status', ['approved', 'paid'])
->where('type', 'penalty') ->where('type', 'penalty')
->whereBetween('date', [$month->startOfMonth()->toDateString(), $month->endOfMonth()->toDateString()]) ->whereBetween('date', [$monthStart, $monthEnd])
->sum('amount'); ->sum('amount');
$gross = $baseSalary + $sessionEarnings + $bonuses; $gross = $baseSalary + $sessionEarnings + $bonuses;
$taxAmount = (int) ($gross * 0.05); $tax = (int) ($gross * 0.05);
$insuranceAmount = $employee ? 50000 : 0; // 500 EGP insurance for employees $insurance = $empId ? 50000 : 0;
$totalDeductions = $penalties + $taxAmount + $insuranceAmount; $totalDed = $penalties + $tax + $insurance;
$net = max(0, $gross - $totalDeductions); $net = max(0, $gross - $totalDed);
$payslipId = DB::table('payslips')->insertGetId([ $payslipId = DB::table('payslips')->insertGetId([
'uuid' => Str::uuid(), 'uuid' => Str::uuid(),
...@@ -449,92 +535,84 @@ public function run(): void ...@@ -449,92 +535,84 @@ public function run(): void
'bonuses' => $bonuses, 'bonuses' => $bonuses,
'penalties' => $penalties, 'penalties' => $penalties,
'advances_deducted' => 0, 'advances_deducted' => 0,
'tax_amount' => $taxAmount, 'tax_amount' => $tax,
'insurance_amount' => $insuranceAmount, 'insurance_amount' => $insurance,
'other_deductions' => 0, 'other_deductions' => 0,
'gross_amount' => $gross, 'gross_amount' => $gross,
'total_deductions' => $totalDeductions, 'total_deductions' => $totalDed,
'net_amount' => $net, 'net_amount' => $net,
'status' => 'paid', 'status' => 'paid',
'approved_by' => $userId, 'approved_by' => $userId,
'approved_at' => $now, 'approved_at' => $now,
'paid_at' => $now, 'paid_at' => $now,
'payment_method' => $employee ? 'bank_transfer' : 'cash', 'payment_method' => $empId ? 'bank_transfer' : 'cash',
'payment_reference' => $employee ? 'TRF-' . now()->format('Ymd') . '-' . $tId : null, 'payment_reference' => $empId ? 'TRF-' . $month->format('Ym') . '-' . ($i + 1) : null,
'created_by' => $userId, 'created_by' => $userId,
'created_at' => $now, 'created_at' => $now,
'updated_at' => $now, 'updated_at' => $now,
]); ]);
// Payslip items
$items = []; $items = [];
$sortOrder = 0; $sort = 0;
if ($baseSalary > 0) { if ($baseSalary > 0) {
$items[] = ['payslip_id' => $payslipId, 'type' => 'base_salary', 'description' => 'الراتب الأساسي', 'quantity' => 1, 'rate' => $baseSalary, 'amount' => $baseSalary, 'is_deduction' => false, 'metadata' => '{}', 'sort_order' => $sortOrder++, 'created_at' => $now, 'updated_at' => $now]; $items[] = ['payslip_id' => $payslipId, 'type' => 'base_salary', 'description' => 'الراتب الأساسي', 'quantity' => 1, 'rate' => $baseSalary, 'amount' => $baseSalary, 'is_deduction' => false, 'metadata' => '{}', 'sort_order' => $sort++, 'created_at' => $now, 'updated_at' => $now];
} }
if ($sessionEarnings > 0) { if ($sessionEarnings > 0) {
$items[] = ['payslip_id' => $payslipId, 'type' => 'session_pay', 'description' => 'أجر الحصص', 'quantity' => 1, 'rate' => $sessionEarnings, 'amount' => $sessionEarnings, 'is_deduction' => false, 'metadata' => '{}', 'sort_order' => $sortOrder++, 'created_at' => $now, 'updated_at' => $now]; $items[] = ['payslip_id' => $payslipId, 'type' => 'session_pay', 'description' => 'أجر الحصص', 'quantity' => 1, 'rate' => $sessionEarnings, 'amount' => $sessionEarnings, 'is_deduction' => false, 'metadata' => '{}', 'sort_order' => $sort++, 'created_at' => $now, 'updated_at' => $now];
} }
if ($bonuses > 0) { if ($bonuses > 0) {
$items[] = ['payslip_id' => $payslipId, 'type' => 'bonus', 'description' => 'مكافآت', 'quantity' => 1, 'rate' => $bonuses, 'amount' => $bonuses, 'is_deduction' => false, 'metadata' => '{}', 'sort_order' => $sortOrder++, 'created_at' => $now, 'updated_at' => $now]; $items[] = ['payslip_id' => $payslipId, 'type' => 'bonus', 'description' => 'مكافآت', 'quantity' => 1, 'rate' => $bonuses, 'amount' => $bonuses, 'is_deduction' => false, 'metadata' => '{}', 'sort_order' => $sort++, 'created_at' => $now, 'updated_at' => $now];
} }
if ($penalties > 0) { if ($penalties > 0) {
$items[] = ['payslip_id' => $payslipId, 'type' => 'penalty', 'description' => 'خصومات', 'quantity' => 1, 'rate' => $penalties, 'amount' => $penalties, 'is_deduction' => true, 'metadata' => '{}', 'sort_order' => $sortOrder++, 'created_at' => $now, 'updated_at' => $now]; $items[] = ['payslip_id' => $payslipId, 'type' => 'penalty', 'description' => 'خصومات', 'quantity' => 1, 'rate' => $penalties, 'amount' => $penalties, 'is_deduction' => true, 'metadata' => '{}', 'sort_order' => $sort++, 'created_at' => $now, 'updated_at' => $now];
} }
if ($taxAmount > 0) { if ($tax > 0) {
$items[] = ['payslip_id' => $payslipId, 'type' => 'tax', 'description' => 'ضريبة دخل 5%', 'quantity' => 1, 'rate' => $taxAmount, 'amount' => $taxAmount, 'is_deduction' => true, 'metadata' => '{}', 'sort_order' => $sortOrder++, 'created_at' => $now, 'updated_at' => $now]; $items[] = ['payslip_id' => $payslipId, 'type' => 'tax', 'description' => 'ضريبة دخل 5%', 'quantity' => 1, 'rate' => $tax, 'amount' => $tax, 'is_deduction' => true, 'metadata' => '{}', 'sort_order' => $sort++, 'created_at' => $now, 'updated_at' => $now];
} }
if ($insuranceAmount > 0) { if ($insurance > 0) {
$items[] = ['payslip_id' => $payslipId, 'type' => 'insurance', 'description' => 'تأمين اجتماعي', 'quantity' => 1, 'rate' => $insuranceAmount, 'amount' => $insuranceAmount, 'is_deduction' => true, 'metadata' => '{}', 'sort_order' => $sortOrder++, 'created_at' => $now, 'updated_at' => $now]; $items[] = ['payslip_id' => $payslipId, 'type' => 'insurance', 'description' => 'تأمين اجتماعي', 'quantity' => 1, 'rate' => $insurance, 'amount' => $insurance, 'is_deduction' => true, 'metadata' => '{}', 'sort_order' => $sort++, 'created_at' => $now, 'updated_at' => $now];
} }
if ($items) { if ($items) {
DB::table('payslip_items')->insert($items); DB::table('payslip_items')->insert($items);
} }
$periodTotalGross += $gross; $periodGross += $gross;
$periodTotalDeductions += $totalDeductions; $periodDed += $totalDed;
$periodTotalNet += $net; $periodNet += $net;
$slipCount++; $slipCount++;
} }
// Update period totals
DB::table('payroll_periods')->where('id', $periodId)->update([ DB::table('payroll_periods')->where('id', $periodId)->update([
'total_gross' => $periodTotalGross, 'total_gross' => $periodGross,
'total_deductions' => $periodTotalDeductions, 'total_deductions' => $periodDed,
'total_net' => $periodTotalNet, 'total_net' => $periodNet,
'payslip_count' => $slipCount, 'payslip_count' => $slipCount,
]); ]);
} }
// ─── Trainer Advances (سلف) ─── // ─── Trainer Advances ───
$advances = [ $advances = [
// Active advance — trainer 1 took 5000 EGP, paying back in 5 installments ['idx' => 0, 'amount' => 500000, 'rem' => 300000, 'inst' => 100000, 'cnt' => 5, 'paid' => 2, 'status' => 'active', 'date' => '2026-05-01', 'reason' => 'سلفة شخصية — مصاريف طبية'],
['trainer_id' => 1, 'amount' => 500000, 'remaining' => 300000, 'installment' => 100000, 'count' => 5, 'paid' => 2, 'status' => 'active', 'date' => '2026-05-01', 'reason' => 'سلفة شخصية — مصاريف طبية'], ['idx' => 3, 'amount' => 300000, 'rem' => 200000, 'inst' => 100000, 'cnt' => 3, 'paid' => 1, 'status' => 'active', 'date' => '2026-06-01', 'reason' => 'سلفة — مصاريف سفر'],
// Active advance — trainer 4 took 3000 EGP ['idx' => 5, 'amount' => 200000, 'rem' => 0, 'inst' => 100000, 'cnt' => 2, 'paid' => 2, 'status' => 'fully_deducted', 'date' => '2026-03-01', 'reason' => 'سلفة شخصية'],
['trainer_id' => 4, 'amount' => 300000, 'remaining' => 200000, 'installment' => 100000, 'count' => 3, 'paid' => 1, 'status' => 'active', 'date' => '2026-06-01', 'reason' => 'سلفة — مصاريف سفر'], ['idx' => 7, 'amount' => 400000, 'rem' => 300000, 'inst' => 100000, 'cnt' => 4, 'paid' => 1, 'status' => 'paused', 'date' => '2026-04-01', 'reason' => 'سلفة طوارئ'],
// Fully deducted advance — trainer 6 repaid everything ['idx' => 11,'amount' => 150000, 'rem' => 150000, 'inst' => 75000, 'cnt' => 2, 'paid' => 0, 'status' => 'cancelled', 'date' => '2026-06-15', 'reason' => 'تم إلغاء الطلب بناءً على رغبة المدرب'],
['trainer_id' => 6, 'amount' => 200000, 'remaining' => 0, 'installment' => 100000, 'count' => 2, 'paid' => 2, 'status' => 'fully_deducted', 'date' => '2026-03-01', 'reason' => 'سلفة شخصية'],
// Paused advance — trainer 8 paused repayment
['trainer_id' => 8, 'amount' => 400000, 'remaining' => 300000, 'installment' => 100000, 'count' => 4, 'paid' => 1, 'status' => 'paused', 'date' => '2026-04-01', 'reason' => 'سلفة طوارئ'],
// Cancelled advance
['trainer_id' => 12, 'amount' => 150000, 'remaining' => 150000, 'installment' => 75000, 'count' => 2, 'paid' => 0, 'status' => 'cancelled', 'date' => '2026-06-15', 'reason' => 'تم إلغاء الطلب بناءً على رغبة المدرب'],
]; ];
foreach ($advances as $adv) { foreach ($advances as $adv) {
DB::table('trainer_advances')->insert([ DB::table('trainer_advances')->insert([
'uuid' => Str::uuid(), 'uuid' => Str::uuid(),
'academy_id' => $academyId, 'academy_id' => $academyId,
'trainer_id' => $adv['trainer_id'], 'trainer_id' => $trainerIds[$adv['idx']],
'amount' => $adv['amount'], 'amount' => $adv['amount'],
'remaining_balance' => $adv['remaining'], 'remaining_balance' => $adv['rem'],
'installment_amount' => $adv['installment'], 'installment_amount' => $adv['inst'],
'installments_count' => $adv['count'], 'installments_count' => $adv['cnt'],
'installments_paid' => $adv['paid'], 'installments_paid' => $adv['paid'],
'reason' => $adv['reason'], 'reason' => $adv['reason'],
'status' => $adv['status'], 'status' => $adv['status'],
'issued_date' => $adv['date'], 'issued_date' => $adv['date'],
'expected_completion_date' => now()->addMonths($adv['count'] - $adv['paid'])->toDateString(), 'expected_completion_date' => now()->addMonths($adv['cnt'] - $adv['paid'])->toDateString(),
'approved_by' => $userId, 'approved_by' => $userId,
'created_by' => $userId, 'created_by' => $userId,
'created_at' => $now, 'created_at' => $now,
...@@ -544,38 +622,35 @@ public function run(): void ...@@ -544,38 +622,35 @@ public function run(): void
// ─── Rate History ─── // ─── Rate History ───
$rateChanges = [ $rateChanges = [
['trainer_id' => 1, 'field' => 'session_rate', 'old' => '12000', 'new' => '15000', 'date' => '2026-01-01', 'notes' => 'زيادة سنوية'], [0, 'session_rate', '12000', '15000', '2026-01-01', 'زيادة سنوية'],
['trainer_id' => 3, 'field' => 'hourly_rate', 'old' => '12000', 'new' => '15000', 'date' => '2026-04-01', 'notes' => 'ترقية بعد 6 أشهر'], [2, 'hourly_rate', '12000', '15000', '2026-04-01', 'ترقية بعد 6 أشهر'],
['trainer_id' => 6, 'field' => 'group_rate', 'old' => '20000', 'new' => '25000', 'date' => '2026-03-01', 'notes' => 'ترقية لرئيس مدربين'], [5, 'group_rate', '20000', '25000', '2026-03-01', 'ترقية لرئيس مدربين'],
['trainer_id' => 12, 'field' => 'player_rate', 'old' => '4000', 'new' => '5000', 'date' => '2026-05-01', 'notes' => 'زيادة بناءً على الأداء'], [11,'player_rate', '4000', '5000', '2026-05-01', 'زيادة بناءً على الأداء'],
['trainer_id' => 4, 'field' => 'session_rate', 'old' => '15000', 'new' => '18000', 'date' => '2026-02-01', 'notes' => 'حصول على شهادة جديدة'], [3, 'session_rate', '15000', '18000', '2026-02-01', 'حصول على شهادة جديدة'],
]; ];
foreach ($rateChanges as [$idx, $field, $old, $new, $date, $notes]) {
foreach ($rateChanges as $rc) {
DB::table('trainer_rate_history')->insert([ DB::table('trainer_rate_history')->insert([
'academy_id' => $academyId, 'academy_id' => $academyId,
'trainer_id' => $rc['trainer_id'], 'trainer_id' => $trainerIds[$idx],
'field' => $rc['field'], 'field' => $field,
'old_value' => $rc['old'], 'old_value' => $old,
'new_value' => $rc['new'], 'new_value' => $new,
'effective_from' => $rc['date'], 'effective_from' => $date,
'changed_by' => $userId, 'changed_by' => $userId,
'notes' => $rc['notes'], 'notes' => $notes,
'created_at' => $now, 'created_at' => $now,
'updated_at' => $now, 'updated_at' => $now,
]); ]);
} }
$this->command->info('HR Demo data seeded successfully!'); $this->command?->info('HR Demo data seeded successfully!');
$this->command->info("- 7 non-trainer employees (1 resigned, 1 on-leave)"); $this->command?->info("- 11 employees (6 staff + 4 trainer-employees + 1 resigned, 1 on-leave)");
$this->command->info("- 4 trainers linked to employee records (salaried/hybrid)"); $this->command?->info("- 12 trainers (4 employee-based + 8 freelancers)");
$this->command->info("- 8 freelancer trainers with various compensation models"); $this->command?->info("- 12 qualifications, availability for all");
$this->command->info("- 12 trainer qualifications"); $this->command?->info("- ~600+ compensation records (3 months)");
$this->command->info("- Availability schedules for all trainers"); $this->command?->info("- 3 payroll periods (2 closed with payslips, 1 open)");
$this->command->info("- 3 months of compensation records (~600+ records)"); $this->command?->info("- 5 advances (active, paused, fully_deducted, cancelled)");
$this->command->info("- 2 closed payroll periods with payslips + 1 open period"); $this->command?->info("- 5 rate history records");
$this->command->info("- 5 advances (active, paused, fully_deducted, cancelled)"); $this->command?->info("- Payroll system ENABLED");
$this->command->info("- 5 rate change history records");
$this->command->info("- Payroll system ENABLED");
} }
} }
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment