Commit 9342a5ee authored by Mahmoud Aglan's avatar Mahmoud Aglan

feat(hr): work schedules, job description cards, org hierarchy

Phase 3: Weekly work schedules
- Migration: hr_weekly_schedules table (per-employee per-day shift times + rest days)
- Seed: import 59 employees' weekly schedules from club's work schedule sheet
- Seed: 13 shift definitions (9-5, 3-11, 10-6, 12-8, etc.)

Phase 7B: Job description cards
- Migration: hr_job_descriptions table (purpose, authority, duties per job title)
- View: printable بطاقة الوصف الوظيفي matching club's official template
- Route: GET /hr/job-titles/{id}/description-card

Phase 8: Org structure hierarchy
- Migration sets parent_id=1 (EXEC) for all other departments
Co-Authored-By: 's avatarClaude Opus 4.6 <noreply@anthropic.com>
parent f61ceb27
...@@ -135,6 +135,29 @@ class JobTitleController extends Controller ...@@ -135,6 +135,29 @@ class JobTitleController extends Controller
return $errors; return $errors;
} }
public function descriptionCard(Request $request, string $id): Response
{
$db = App::getInstance()->db();
$jobTitle = $db->selectOne(
"SELECT * FROM hr_job_titles WHERE id = ? AND is_archived = 0",
[(int) $id]
);
if (!$jobTitle) {
return $this->redirect('/hr/job-titles')->withError('المسمى الوظيفي غير موجود');
}
$description = $db->selectOne(
"SELECT * FROM hr_job_descriptions WHERE job_title_id = ? AND is_archived = 0 ORDER BY id DESC LIMIT 1",
[(int) $id]
);
return $this->view('HR.Views.forms.job-description', [
'jobTitle' => $jobTitle,
'description' => $description,
]);
}
private function flashErrorsAndRedirect(array $errors, Request $request, string $url): Response private function flashErrorsAndRedirect(array $errors, Request $request, string $url): Response
{ {
$session = App::getInstance()->session(); $session = App::getInstance()->session();
......
...@@ -23,6 +23,7 @@ return [ ...@@ -23,6 +23,7 @@ return [
['GET', '/hr/job-titles/{id:\d+}/edit', 'HR\Controllers\JobTitleController@edit', ['auth'], 'hr.job_title.manage'], ['GET', '/hr/job-titles/{id:\d+}/edit', 'HR\Controllers\JobTitleController@edit', ['auth'], 'hr.job_title.manage'],
['POST', '/hr/job-titles/{id:\d+}', 'HR\Controllers\JobTitleController@update', ['auth', 'csrf'], 'hr.job_title.manage'], ['POST', '/hr/job-titles/{id:\d+}', 'HR\Controllers\JobTitleController@update', ['auth', 'csrf'], 'hr.job_title.manage'],
['POST', '/hr/job-titles/{id:\d+}/archive', 'HR\Controllers\JobTitleController@archive', ['auth', 'csrf'], 'hr.job_title.manage'], ['POST', '/hr/job-titles/{id:\d+}/archive', 'HR\Controllers\JobTitleController@archive', ['auth', 'csrf'], 'hr.job_title.manage'],
['GET', '/hr/job-titles/{id:\d+}/description-card', 'HR\Controllers\JobTitleController@descriptionCard', ['auth'], 'hr.job_title.view'],
// ── Employee Profiles ── // ── Employee Profiles ──
['GET', '/hr/employees', 'HR\Controllers\EmployeeProfileController@index', ['auth'], 'hr.employee.view'], ['GET', '/hr/employees', 'HR\Controllers\EmployeeProfileController@index', ['auth'], 'hr.employee.view'],
......
<?php
/** @var array $jobTitle */
/** @var array|null $description */
$__template->layout('Layout.print');
$__template->section('title', 'بطاقة الوصف الوظيفي');
$__template->section('content');
?>
<div class="print-page" style="direction: rtl; text-align: right; font-family: 'Cairo', sans-serif; max-width: 700px; margin: 0 auto; padding: 40px;">
<div style="text-align: center; margin-bottom: 30px;">
<p style="margin: 0; font-size: 13px; color: #666;">H.R</p>
<h3 style="margin: 10px 0 5px;">شركة أوسي سبورت للخدمات الرياضية</h3>
<p style="margin: 0; color: #666;">نادي النادي فرع شيراتون</p>
<hr style="border: 1px solid #333; margin: 15px 0;">
<h2 style="margin: 15px 0;">بطاقة الوصف الوظيفي ( <?= e($jobTitle['name_ar'] ?? '') ?> )</h2>
</div>
<div style="font-size: 14px; line-height: 2;">
<h4 style="margin: 20px 0 10px; background: #f5f5f5; padding: 5px 10px;">أولاً — معلومات خاصة بالوظيفة:</h4>
<table style="width:100%; border-collapse: collapse; font-size: 14px;">
<tr>
<td style="padding: 8px; border: 1px solid #ddd; width: 30%; font-weight: bold; background: #fafafa;">المسمى الوظيفي</td>
<td style="padding: 8px; border: 1px solid #ddd;"><?= e($jobTitle['name_ar'] ?? '') ?></td>
</tr>
<tr>
<td style="padding: 8px; border: 1px solid #ddd; font-weight: bold; background: #fafafa;">الغرض الرئيسي من الوظيفة</td>
<td style="padding: 8px; border: 1px solid #ddd;"><?= e($description['purpose_ar'] ?? '..............................') ?></td>
</tr>
<tr>
<td style="padding: 8px; border: 1px solid #ddd; font-weight: bold; background: #fafafa;">الجهة المسئولة عن الوظيفة</td>
<td style="padding: 8px; border: 1px solid #ddd;"><?= e($description['responsible_authority'] ?? '..............................') ?></td>
</tr>
<tr>
<td style="padding: 8px; border: 1px solid #ddd; font-weight: bold; background: #fafafa;">العلاقة مع الوظائف الأخرى</td>
<td style="padding: 8px; border: 1px solid #ddd;"><?= e($description['relationships'] ?? 'مباشرة') ?></td>
</tr>
<tr>
<td style="padding: 8px; border: 1px solid #ddd; font-weight: bold; background: #fafafa;">حدود الصلاحيات</td>
<td style="padding: 8px; border: 1px solid #ddd;"><?= e($description['authority_limits'] ?? '..............................') ?></td>
</tr>
<tr>
<td style="padding: 8px; border: 1px solid #ddd; font-weight: bold; background: #fafafa;">المؤهل العلمي</td>
<td style="padding: 8px; border: 1px solid #ddd;"><?= e($description['required_qualification'] ?? '..............................') ?></td>
</tr>
</table>
<h4 style="margin: 25px 0 10px; background: #f5f5f5; padding: 5px 10px;">ثانياً — المهام الخاصة بواجبات ومسئوليات الوظيفة:</h4>
<?php if (!empty($description['duties_ar'])): ?>
<div style="padding: 10px; white-space: pre-line;"><?= e($description['duties_ar']) ?></div>
<?php else: ?>
<div style="padding: 10px;">
<p>* ..................................................................................................</p>
<p>* ..................................................................................................</p>
<p>* ..................................................................................................</p>
<p>* ..................................................................................................</p>
<p>* ..................................................................................................</p>
</div>
<?php endif; ?>
</div>
<div style="margin-top: 60px; display: flex; justify-content: space-between;">
<div style="text-align: center;">
<p style="font-weight: bold;">مدير الموارد البشرية</p>
<br><br>
<p>التوقيع: ..................</p>
</div>
<div style="text-align: center;">
<p style="font-weight: bold;">المدير التنفيذي</p>
<br><br>
<p>التوقيع: ..................</p>
</div>
</div>
</div>
<style>
@media print {
body { margin: 0; }
.print-page { padding: 20px !important; }
.no-print { display: none !important; }
}
</style>
<?php $__template->endSection(); ?>
<?php
declare(strict_types=1);
return [
'up' => "
CREATE TABLE IF NOT EXISTS hr_weekly_schedules (
id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
employee_profile_id INT UNSIGNED NOT NULL,
day_of_week ENUM('saturday','sunday','monday','tuesday','wednesday','thursday','friday') NOT NULL,
shift_id INT UNSIGNED NULL,
start_time TIME NULL,
end_time TIME NULL,
is_rest_day TINYINT(1) NOT NULL DEFAULT 0,
notes VARCHAR(200) NULL,
is_active TINYINT(1) NOT NULL DEFAULT 1,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
FOREIGN KEY (employee_profile_id) REFERENCES hr_employee_profiles(id) ON DELETE CASCADE,
FOREIGN KEY (shift_id) REFERENCES hr_shifts(id) ON DELETE SET NULL,
UNIQUE KEY uk_emp_day (employee_profile_id, day_of_week, is_active)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE TABLE IF NOT EXISTS hr_job_descriptions (
id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
job_title_id INT UNSIGNED NOT NULL,
purpose_ar TEXT NULL,
responsible_authority VARCHAR(200) NULL,
relationships VARCHAR(200) NULL,
authority_limits TEXT NULL,
required_qualification VARCHAR(200) NULL,
duties_ar TEXT NULL,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
created_by INT UNSIGNED NULL,
is_archived TINYINT(1) NOT NULL DEFAULT 0,
FOREIGN KEY (job_title_id) REFERENCES hr_job_titles(id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
UPDATE hr_departments SET parent_id = 1 WHERE department_code != 'EXEC' AND parent_id IS NULL AND is_archived = 0
",
'down' => "
DROP TABLE IF EXISTS hr_weekly_schedules;
DROP TABLE IF EXISTS hr_job_descriptions;
UPDATE hr_departments SET parent_id = NULL WHERE parent_id = 1
",
];
<?php
declare(strict_types=1);
use App\Core\Database;
return function (Database $db): void {
$schedules = [
['name' => 'مجدي فتحي الخمري', 'sat' => 'open', 'sun' => 'open', 'mon' => 'open', 'tue' => 'open', 'wed' => 'open', 'thu' => 'rest', 'fri' => 'rest'],
['name' => 'محمد نبيل السيد', 'sat' => '9-17', 'sun' => '9-17', 'mon' => '12-20', 'tue' => 'rest', 'wed' => '9-17', 'thu' => '12-20', 'fri' => 'rest'],
['name' => 'محمد السيد عبد الفتاح', 'sat' => '15-23', 'sun' => 'rest', 'mon' => '15-23', 'tue' => '15-23', 'wed' => '15-23', 'thu' => '15-23', 'fri' => 'rest'],
['name' => 'ايمان احمد', 'sat' => '12-20', 'sun' => '15-23', 'mon' => 'rest', 'tue' => '15-23', 'wed' => '15-23', 'thu' => 'rest', 'fri' => '10-23'],
['name' => 'رؤى محمد حسن', 'sat' => '10-18', 'sun' => '10-18', 'mon' => '10-18', 'tue' => 'rest', 'wed' => '10-18', 'thu' => '10-18', 'fri' => 'rest'],
['name' => 'محمد فودة', 'sat' => '15-23', 'sun' => '15-23', 'mon' => 'rest', 'tue' => '15-23', 'wed' => '15-23', 'thu' => '15-23', 'fri' => 'rest'],
['name' => 'محمد عبد الحميد محمد', 'sat' => '9-17', 'sun' => '9-17', 'mon' => '9-17', 'tue' => 'rest', 'wed' => 'rest', 'thu' => '9-17', 'fri' => '9-17'],
['name' => 'أحمد نبيل محمود', 'sat' => '9-17', 'sun' => 'rest', 'mon' => '9-17', 'tue' => '9-17', 'wed' => '9-17', 'thu' => 'rest', 'fri' => '9-17'],
['name' => 'عمر محمد طاهر', 'sat' => 'rest', 'sun' => '15-23', 'mon' => 'rest', 'tue' => '15-23', 'wed' => '15-23', 'thu' => '15-23', 'fri' => '15-23'],
['name' => 'منار هشام محمد محمود', 'sat' => '15-23', 'sun' => '15-23', 'mon' => '15-23', 'tue' => '15-23', 'wed' => '15-23', 'thu' => 'rest', 'fri' => 'rest'],
['name' => 'محمد ماهر جابر احمد', 'sat' => 'rest', 'sun' => 'rest', 'mon' => '15-23', 'tue' => '15-23', 'wed' => '15-23', 'thu' => '15-23', 'fri' => '15-23'],
['name' => 'مصطفى عبد الفتاح محمد', 'sat' => '15-23', 'sun' => '15-23', 'mon' => '15-23', 'tue' => '15-23', 'wed' => '15-23', 'thu' => 'rest', 'fri' => 'rest'],
['name' => 'عبد الرحمن البدري', 'sat' => 'rest', 'sun' => '15-23', 'mon' => '15-23', 'tue' => '15-23', 'wed' => '15-23', 'thu' => '15-23', 'fri' => 'rest'],
['name' => 'مصطفى البدري', 'sat' => 'rest', 'sun' => 'rest', 'mon' => '9-17', 'tue' => '9-17', 'wed' => '9-17', 'thu' => '9-17', 'fri' => '15-23'],
['name' => 'هاني محمد', 'sat' => '9-17', 'sun' => '9-17', 'mon' => '9-17', 'tue' => '9-17', 'wed' => 'rest', 'thu' => 'rest', 'fri' => '9-17'],
['name' => 'أسامة هاشم', 'sat' => 'rest', 'sun' => '9-17', 'mon' => '9-17', 'tue' => 'rest', 'wed' => '9-17', 'thu' => '9-17', 'fri' => '15-23'],
['name' => 'احمد حسين عبد المنعم', 'sat' => '15-23', 'sun' => '15-23', 'mon' => '15-23', 'tue' => '15-23', 'wed' => '15-23', 'thu' => 'rest', 'fri' => 'rest'],
['name' => 'حمدي عبده', 'sat' => '9-17', 'sun' => 'rest', 'mon' => 'rest', 'tue' => '9-17', 'wed' => '9-17', 'thu' => '9-17', 'fri' => '9-17'],
['name' => 'احمد عبد الخالق اسماعيل', 'sat' => '8-15', 'sun' => '8-15', 'mon' => '16-23', 'tue' => '16-23', 'wed' => '16-23', 'thu' => 'rest', 'fri' => '16-23'],
['name' => 'احمد محمد سعد', 'sat' => '15-23', 'sun' => '15-23', 'mon' => '15-23', 'tue' => 'rest', 'wed' => '15-23', 'thu' => '15-23', 'fri' => 'rest'],
['name' => 'مصطفي خالد محمد على', 'sat' => 'rest', 'sun' => 'rest', 'mon' => '9-17', 'tue' => '9-17', 'wed' => '9-17', 'thu' => '9-17', 'fri' => '9-17'],
['name' => 'نجلاء محمد السيد', 'sat' => '14-22', 'sun' => '14-22', 'mon' => '14-22', 'tue' => 'rest', 'wed' => '14-22', 'thu' => '14-22', 'fri' => 'rest'],
['name' => 'محمود محمد أشرف', 'sat' => '9-16', 'sun' => '9-16', 'mon' => '9-16', 'tue' => '9-16', 'wed' => '9-16', 'thu' => '9-16', 'fri' => 'rest'],
['name' => 'اسلام محمد احمد محمد', 'sat' => '15-23', 'sun' => '15-23', 'mon' => '15-23', 'tue' => '15-23', 'wed' => 'rest', 'thu' => 'rest', 'fri' => '15-23'],
['name' => 'رشا شريف', 'sat' => '9-17', 'sun' => '9-17', 'mon' => '9-17', 'tue' => 'rest', 'wed' => '9-17', 'thu' => '9-17', 'fri' => 'rest'],
['name' => 'رشا بخيت فراج', 'sat' => '11-23', 'sun' => '16-23', 'mon' => '16-23', 'tue' => '16-23', 'wed' => '16-23', 'thu' => 'rest', 'fri' => 'rest'],
['name' => 'ولاء احمد', 'sat' => '9-17', 'sun' => 'rest', 'mon' => '9-17', 'tue' => '9-17', 'wed' => '9-17', 'thu' => '9-17', 'fri' => 'rest'],
['name' => 'مراد صالح', 'sat' => '15-23', 'sun' => 'other', 'mon' => 'rest', 'tue' => 'other', 'wed' => '15-23', 'thu' => '15-23', 'fri' => 'rest'],
['name' => 'ميادة محمد خضير', 'sat' => 'rest', 'sun' => '9-17', 'mon' => '9-17', 'tue' => '9-17', 'wed' => '9-17', 'thu' => '9-17', 'fri' => 'rest'],
['name' => 'تهانى محسن محمد محجوب', 'sat' => 'rest', 'sun' => '12-20', 'mon' => '13-21', 'tue' => '13-21', 'wed' => 'rest', 'thu' => '9-17', 'fri' => '12-20'],
['name' => 'وليد محمد غندور على', 'sat' => '9-17', 'sun' => '15-23', 'mon' => '15-23', 'tue' => '15-23', 'wed' => '15-23', 'thu' => '15-23', 'fri' => 'rest'],
['name' => 'علا سيد مصطفي', 'sat' => '15-23', 'sun' => 'rest', 'mon' => 'rest', 'tue' => '15-23', 'wed' => '15-23', 'thu' => '15-23', 'fri' => '15-23'],
['name' => 'علاء الدين محمد', 'sat' => 'other', 'sun' => '9-17', 'mon' => '12-20', 'tue' => '12-20', 'wed' => '12-20', 'thu' => 'rest', 'fri' => 'rest'],
['name' => 'مصطفي سامي', 'sat' => '15-23', 'sun' => '15-23', 'mon' => '15-23', 'tue' => '15-23', 'wed' => 'rest', 'thu' => 'rest', 'fri' => '15-23'],
['name' => 'إسلام محمد حسن', 'sat' => 'rest', 'sun' => '15-23', 'mon' => '15-23', 'tue' => '15-23', 'wed' => '15-23', 'thu' => '15-23', 'fri' => 'rest'],
['name' => 'صلاح هانى صلاح', 'sat' => 'rest', 'sun' => '9-17', 'mon' => '9-17', 'tue' => '9-17', 'wed' => '9-17', 'thu' => '9-17', 'fri' => 'rest'],
['name' => 'طعمه نبيل', 'sat' => '9-17', 'sun' => '9-17', 'mon' => 'rest', 'tue' => 'rest', 'wed' => '9-17', 'thu' => '9-17', 'fri' => '9-17'],
['name' => 'محمد إسماعيل', 'sat' => '15-23', 'sun' => 'rest', 'mon' => '15-23', 'tue' => '15-23', 'wed' => '15-23', 'thu' => 'rest', 'fri' => '15-23'],
['name' => 'ريهام طلعت', 'sat' => '9-17', 'sun' => 'rest', 'mon' => '9-17', 'tue' => '9-17', 'wed' => 'rest', 'thu' => '9-17', 'fri' => '9-17'],
['name' => 'ايمن عبد ربه', 'sat' => '15-23', 'sun' => 'rest', 'mon' => '15-23', 'tue' => '15-23', 'wed' => 'rest', 'thu' => '15-23', 'fri' => '15-23'],
['name' => 'محمد عبد العزيز المهدي', 'sat' => '15-23', 'sun' => '15-23', 'mon' => 'rest', 'tue' => '12-20', 'wed' => '15-23', 'thu' => 'rest', 'fri' => '12-20'],
['name' => 'زياد فرج أحمد', 'sat' => 'rest', 'sun' => '10-18', 'mon' => '10-18', 'tue' => 'rest', 'wed' => '10-18', 'thu' => '15-23', 'fri' => '10-18'],
['name' => 'شيماء حمدي طاهر', 'sat' => 'rest', 'sun' => '9-17', 'mon' => '9-17', 'tue' => '9-17', 'wed' => 'rest', 'thu' => '9-17', 'fri' => '9-17'],
['name' => 'إسلام سمير أحمد', 'sat' => '9-17', 'sun' => 'rest', 'mon' => '9-17', 'tue' => '9-17', 'wed' => '9-17', 'thu' => '9-17', 'fri' => 'rest'],
['name' => 'اسلام محمد مصطفى يوسف', 'sat' => '15-23', 'sun' => '15-23', 'mon' => '15-23', 'tue' => 'rest', 'wed' => '15-23', 'thu' => 'rest', 'fri' => '15-23'],
['name' => 'محمد يحيي', 'sat' => '9-17', 'sun' => 'rest', 'mon' => 'rest', 'tue' => '15-23', 'wed' => '15-23', 'thu' => '9-17', 'fri' => '9-17'],
['name' => 'عمرو محمد عبد العظيم السيد', 'sat' => 'rest', 'sun' => '18-22', 'mon' => 'rest', 'tue' => '18-22', 'wed' => 'rest', 'thu' => '18-22', 'fri' => 'rest'],
['name' => 'خالد محمد أحمد محمد', 'sat' => 'rest', 'sun' => '18-22', 'mon' => 'rest', 'tue' => 'rest', 'wed' => '18-22', 'thu' => 'rest', 'fri' => 'rest'],
['name' => 'حسام مصطفى السيد مصطفى', 'sat' => '15-23', 'sun' => '15-23', 'mon' => '9-17', 'tue' => '9-17', 'wed' => 'rest', 'thu' => '15-23', 'fri' => 'rest'],
['name' => 'إسماعيل محمد حسن', 'sat' => '16-23', 'sun' => 'rest', 'mon' => '16-23', 'tue' => '16-23', 'wed' => '16-23', 'thu' => 'rest', 'fri' => '11-23'],
['name' => 'امل ابراهيم حسين', 'sat' => '9-17', 'sun' => '9-17', 'mon' => 'rest', 'tue' => 'rest', 'wed' => '9-17', 'thu' => '9-17', 'fri' => '9-17'],
['name' => 'يوسف محمد مصطفي', 'sat' => '15-23', 'sun' => '15-23', 'mon' => '15-23', 'tue' => 'rest', 'wed' => '15-23', 'thu' => '15-23', 'fri' => 'rest'],
['name' => 'هبة محمود محمود', 'sat' => 'rest', 'sun' => '9-17', 'mon' => '9-17', 'tue' => 'rest', 'wed' => '9-17', 'thu' => 'rest', 'fri' => 'rest'],
['name' => 'محمد سعد محمد', 'sat' => '17-23', 'sun' => 'rest', 'mon' => 'rest', 'tue' => '17-23', 'wed' => '17-23', 'thu' => 'rest', 'fri' => 'rest'],
['name' => 'ميرفت حنفي', 'sat' => 'rest', 'sun' => '9-17', 'mon' => '9-17', 'tue' => '9-17', 'wed' => '9-17', 'thu' => '9-17', 'fri' => 'rest'],
['name' => 'مصطفى سيد علي', 'sat' => '17-23', 'sun' => 'rest', 'mon' => 'rest', 'tue' => '17-23', 'wed' => '17-23', 'thu' => 'rest', 'fri' => 'rest'],
['name' => 'غادة جمال', 'sat' => 'rest', 'sun' => '10-18', 'mon' => '10-18', 'tue' => '14-22', 'wed' => '10-18', 'thu' => '10-18', 'fri' => 'rest'],
['name' => 'هالة كارم', 'sat' => '9-16', 'sun' => 'rest', 'mon' => '9-16', 'tue' => '9-16', 'wed' => '9-16', 'thu' => 'rest', 'fri' => '9-20'],
['name' => 'محمد عبد المنعم', 'sat' => '15-23', 'sun' => '15-23', 'mon' => '15-23', 'tue' => 'rest', 'wed' => 'rest', 'thu' => '15-23', 'fri' => '15-23'],
['name' => 'احمد عبد الله احمد بكير', 'sat' => 'rest', 'sun' => 'rest', 'mon' => 'rest', 'tue' => 'rest', 'wed' => 'rest', 'thu' => 'rest', 'fri' => 'open'],
];
$dayMap = [
'sat' => 'saturday',
'sun' => 'sunday',
'mon' => 'monday',
'tue' => 'tuesday',
'wed' => 'wednesday',
'thu' => 'thursday',
'fri' => 'friday',
];
$imported = 0;
$skipped = 0;
foreach ($schedules as $sched) {
$name = trim($sched['name']);
$profile = $db->selectOne(
"SELECT id FROM hr_employee_profiles
WHERE CONCAT(first_name_ar, ' ', last_name_ar) LIKE ?
AND is_archived = 0
LIMIT 1",
['%' . $name . '%']
);
if (!$profile) {
$nameParts = explode(' ', $name);
$firstName = $nameParts[0];
$profile = $db->selectOne(
"SELECT id FROM hr_employee_profiles
WHERE first_name_ar = ? AND is_archived = 0
LIMIT 1",
[$firstName]
);
}
if (!$profile) {
$skipped++;
continue;
}
$profileId = (int) $profile['id'];
$existing = $db->selectOne(
"SELECT 1 FROM hr_weekly_schedules WHERE employee_profile_id = ? AND is_active = 1 LIMIT 1",
[$profileId]
);
if ($existing) {
$skipped++;
continue;
}
foreach ($dayMap as $key => $dayName) {
$val = $sched[$key];
if ($val === 'rest') {
$db->insert('hr_weekly_schedules', [
'employee_profile_id' => $profileId,
'day_of_week' => $dayName,
'is_rest_day' => 1,
'start_time' => null,
'end_time' => null,
'created_at' => date('Y-m-d H:i:s'),
'updated_at' => date('Y-m-d H:i:s'),
]);
} elseif ($val === 'open' || $val === 'other') {
$db->insert('hr_weekly_schedules', [
'employee_profile_id' => $profileId,
'day_of_week' => $dayName,
'is_rest_day' => 0,
'start_time' => null,
'end_time' => null,
'notes' => $val === 'open' ? 'شفت مفتوح' : 'فرع آخر',
'created_at' => date('Y-m-d H:i:s'),
'updated_at' => date('Y-m-d H:i:s'),
]);
} else {
$parts = explode('-', $val);
$startHour = (int) $parts[0];
$endHour = (int) $parts[1];
$startTime = sprintf('%02d:00:00', $startHour);
$endTime = sprintf('%02d:00:00', $endHour);
$db->insert('hr_weekly_schedules', [
'employee_profile_id' => $profileId,
'day_of_week' => $dayName,
'is_rest_day' => 0,
'start_time' => $startTime,
'end_time' => $endTime,
'created_at' => date('Y-m-d H:i:s'),
'updated_at' => date('Y-m-d H:i:s'),
]);
}
}
$imported++;
}
echo "Work schedules import: {$imported} employees assigned, {$skipped} skipped\n";
};
<?php
declare(strict_types=1);
use App\Core\Database;
return function (Database $db): void {
$shifts = [
['name_ar' => 'وردية صباحية 9-5', 'start' => '09:00', 'end' => '17:00', 'hours' => 8],
['name_ar' => 'وردية مسائية 3-11', 'start' => '15:00', 'end' => '23:00', 'hours' => 8],
['name_ar' => 'وردية 10-6', 'start' => '10:00', 'end' => '18:00', 'hours' => 8],
['name_ar' => 'وردية 12-8', 'start' => '12:00', 'end' => '20:00', 'hours' => 8],
['name_ar' => 'وردية 8-3', 'start' => '08:00', 'end' => '15:00', 'hours' => 7],
['name_ar' => 'وردية 4-11', 'start' => '16:00', 'end' => '23:00', 'hours' => 7],
['name_ar' => 'وردية 2-10', 'start' => '14:00', 'end' => '22:00', 'hours' => 8],
['name_ar' => 'وردية 9-4', 'start' => '09:00', 'end' => '16:00', 'hours' => 7],
['name_ar' => 'وردية 5-11', 'start' => '17:00', 'end' => '23:00', 'hours' => 6],
['name_ar' => 'وردية طبية 6-10', 'start' => '18:00', 'end' => '22:00', 'hours' => 4],
['name_ar' => 'وردية 11-11', 'start' => '11:00', 'end' => '23:00', 'hours' => 12],
['name_ar' => 'وردية 1-9', 'start' => '13:00', 'end' => '21:00', 'hours' => 8],
['name_ar' => 'وردية 9-8', 'start' => '09:00', 'end' => '20:00', 'hours' => 11],
];
$inserted = 0;
foreach ($shifts as $shift) {
$exists = $db->selectOne(
"SELECT id FROM hr_shifts WHERE start_time = ? AND end_time = ? AND is_active = 1",
[$shift['start'] . ':00', $shift['end'] . ':00']
);
if (!$exists) {
$db->insert('hr_shifts', [
'name_ar' => $shift['name_ar'],
'start_time' => $shift['start'] . ':00',
'end_time' => $shift['end'] . ':00',
'working_hours' => number_format((float) $shift['hours'], 2, '.', ''),
'is_night_shift' => (int) ($shift['start'] >= '15:00' ? 1 : 0),
'is_active' => 1,
'created_at' => date('Y-m-d H:i:s'),
]);
$inserted++;
}
}
echo "Shift definitions: {$inserted} new shifts inserted\n";
};
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