Commit 23dd0208 authored by Mahmoud Aglan's avatar Mahmoud Aglan

feat(hr): 2025 Labour Law compliance + employee import + payroll alignment

Phase 1 - Leave System (2025 Law):
- Annual leave: 15d (1st year) → 21d (after 1y) → 30d (10y+/age 50+) → 45d (disability)
- Maternity: 90 → 120 days (4 months), hajj: now PAID with 5y service min
- New leave types: childcare (unpaid, 3 career, 24mo gap), paternity (1d, 3 career), exam
- Casual leave: enforce max 2 consecutive days
- Childcare gap enforcement, service months validation

Phase 2 - Employee Import:
- Extract 75 employees from HR registry Excel → JSON seed data
- 34 Bank of Alexandria accounts extracted for salary transfers
- 14 departments + all job titles auto-created from registry
- Migration adds variable_salary + total_allowances columns

Phase 4 - Payroll Alignment (July 2026 format):
- Solidarity fund: 0.25% of gross (صندوق التكافل)
- Stamp duty: 3% of net (دمغة عادية وإضافية) - now percentage-based
- Emergency fund + VAT config keys added
- calculation_json includes full breakdown

Phase 5 - Bank Transfer Export:
- BankTransferService generates transfer data from payroll runs
- CSV export with BOM for Arabic compatibility
- View + route: /hr/payroll/periods/{id}/bank-transfer

Phase 6 - Performance Evaluation:
- Seed 10-criteria template matching club's official form (100 points)
- Dual evaluator support (direct manager + general supervisor)
- 5-tier rating labels (ضعيف → ممتاز)

Phase 7 - Reports & Forms:
- Work receipt printable form (إقرار استلام العمل)
- Workforce statement report (بيان القوة الفعلية)
Co-Authored-By: 's avatarClaude Opus 4.6 <noreply@anthropic.com>
parent 9585e99f
...@@ -457,6 +457,24 @@ class EmployeeProfileController extends Controller ...@@ -457,6 +457,24 @@ class EmployeeProfileController extends Controller
return $errors; return $errors;
} }
public function workReceipt(Request $request, string $id): Response
{
$this->authorize('hr.employee.view');
$profile = HrEmployeeProfile::find((int) $id);
if (!$profile) {
return $this->redirect('/hr/employees')->withError('الموظف غير موجود');
}
$db = App::getInstance()->db();
$jobTitle = $db->selectOne("SELECT name_ar FROM hr_job_titles WHERE id = ?", [$profile->job_title_id ?? 0]);
return $this->view('HR.Views.forms.work-receipt', [
'profile' => $profile,
'job_title' => $jobTitle['name_ar'] ?? '',
]);
}
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();
......
...@@ -8,6 +8,7 @@ use App\Core\Request; ...@@ -8,6 +8,7 @@ use App\Core\Request;
use App\Core\Response; use App\Core\Response;
use App\Core\App; use App\Core\App;
use App\Modules\HR\Models\HrDepartment; use App\Modules\HR\Models\HrDepartment;
use App\Modules\HR\Services\WorkforceReportService;
class HrReportController extends Controller class HrReportController extends Controller
{ {
...@@ -267,4 +268,13 @@ class HrReportController extends Controller ...@@ -267,4 +268,13 @@ class HrReportController extends Controller
'days' => $days, 'days' => $days,
]); ]);
} }
public function workforceStatement(Request $request): Response
{
$this->authorize('hr.report.view');
$data = WorkforceReportService::getWorkforceStatement();
return $this->view('HR.Views.reports.workforce_statement', $data);
}
} }
...@@ -13,6 +13,7 @@ use App\Modules\HR\Models\HrPayrollRun; ...@@ -13,6 +13,7 @@ use App\Modules\HR\Models\HrPayrollRun;
use App\Modules\HR\Models\HrEmployeeProfile; use App\Modules\HR\Models\HrEmployeeProfile;
use App\Modules\HR\Services\PayrollCalculationService; use App\Modules\HR\Services\PayrollCalculationService;
use App\Modules\HR\Services\SalarySlipService; use App\Modules\HR\Services\SalarySlipService;
use App\Modules\HR\Services\BankTransferService;
use App\Core\EventBus; use App\Core\EventBus;
class PayrollController extends Controller class PayrollController extends Controller
...@@ -300,6 +301,35 @@ class PayrollController extends Controller ...@@ -300,6 +301,35 @@ class PayrollController extends Controller
return $this->view('HR.Views.payroll.salary_slip', $slipData); return $this->view('HR.Views.payroll.salary_slip', $slipData);
} }
public function bankTransfer(Request $request, string $id): Response
{
$this->authorize('hr.payroll.view');
$result = BankTransferService::generateTransferData((int) $id);
if (!$result['success']) {
return $this->redirect('/hr/payroll/periods/' . $id)->withError($result['error']);
}
return $this->view('HR.Views.payroll.bank_transfer', $result);
}
public function bankTransferCsv(Request $request, string $id): Response
{
$this->authorize('hr.payroll.view');
$csv = BankTransferService::generateCsv((int) $id);
if ($csv === null) {
return $this->redirect('/hr/payroll/periods/' . $id)->withError('لا يوجد بيانات لتصدير كشف البنك');
}
$period = App::getInstance()->db()->selectOne("SELECT * FROM hr_payroll_periods WHERE id = ?", [(int) $id]);
$filename = 'bank_transfer_' . ($period['year'] ?? '') . '_' . ($period['month'] ?? '') . '.csv';
return (new Response($csv, 200))
->header('Content-Type', 'text/csv; charset=utf-8')
->header('Content-Disposition', 'attachment; filename="' . $filename . '"');
}
public function mySlips(Request $request): Response public function mySlips(Request $request): Response
{ {
$employee = $this->currentEmployee(); $employee = $this->currentEmployee();
......
...@@ -35,6 +35,7 @@ return [ ...@@ -35,6 +35,7 @@ return [
['GET', '/hr/employees/{id:\d+}/salary', 'HR\Controllers\EmployeeProfileController@salary', ['auth'], 'hr.employee.view_salary'], ['GET', '/hr/employees/{id:\d+}/salary', 'HR\Controllers\EmployeeProfileController@salary', ['auth'], 'hr.employee.view_salary'],
['POST', '/hr/employees/{id:\d+}/salary', 'HR\Controllers\EmployeeProfileController@updateSalary',['auth', 'csrf'], 'hr.employee.manage_salary'], ['POST', '/hr/employees/{id:\d+}/salary', 'HR\Controllers\EmployeeProfileController@updateSalary',['auth', 'csrf'], 'hr.employee.manage_salary'],
['GET', '/hr/employees/search-json', 'HR\Controllers\EmployeeProfileController@searchJson', ['auth'], 'hr.employee.view'], ['GET', '/hr/employees/search-json', 'HR\Controllers\EmployeeProfileController@searchJson', ['auth'], 'hr.employee.view'],
['GET', '/hr/employees/{id:\d+}/work-receipt', 'HR\Controllers\EmployeeProfileController@workReceipt',['auth'], 'hr.employee.view'],
// ── Contracts ── // ── Contracts ──
['GET', '/hr/contracts', 'HR\Controllers\ContractController@index', ['auth'], 'hr.contract.view'], ['GET', '/hr/contracts', 'HR\Controllers\ContractController@index', ['auth'], 'hr.contract.view'],
...@@ -99,6 +100,8 @@ return [ ...@@ -99,6 +100,8 @@ return [
['GET', '/hr/payroll/runs/{id:\d+}', 'HR\Controllers\PayrollController@showRun', ['auth'], 'hr.payroll.view'], ['GET', '/hr/payroll/runs/{id:\d+}', 'HR\Controllers\PayrollController@showRun', ['auth'], 'hr.payroll.view'],
['GET', '/hr/payroll/runs/{id:\d+}/slip', 'HR\Controllers\PayrollController@salarySlip', ['auth'], 'hr.payroll.view'], ['GET', '/hr/payroll/runs/{id:\d+}/slip', 'HR\Controllers\PayrollController@salarySlip', ['auth'], 'hr.payroll.view'],
['GET', '/hr/payroll/my-slips', 'HR\Controllers\PayrollController@mySlips', ['auth'], 'hr.payslip.view_own'], ['GET', '/hr/payroll/my-slips', 'HR\Controllers\PayrollController@mySlips', ['auth'], 'hr.payslip.view_own'],
['GET', '/hr/payroll/periods/{id:\d+}/bank-transfer', 'HR\Controllers\PayrollController@bankTransfer', ['auth'], 'hr.payroll.view'],
['GET', '/hr/payroll/periods/{id:\d+}/bank-transfer/csv','HR\Controllers\PayrollController@bankTransferCsv', ['auth'], 'hr.payroll.view'],
// ── Insurance ── // ── Insurance ──
['GET', '/hr/insurance', 'HR\Controllers\InsuranceController@index', ['auth'], 'hr.insurance.view'], ['GET', '/hr/insurance', 'HR\Controllers\InsuranceController@index', ['auth'], 'hr.insurance.view'],
...@@ -216,4 +219,5 @@ return [ ...@@ -216,4 +219,5 @@ return [
['GET', '/hr/reports/tax-summary', 'HR\Controllers\HrReportController@taxSummary', ['auth'], 'hr.report.view'], ['GET', '/hr/reports/tax-summary', 'HR\Controllers\HrReportController@taxSummary', ['auth'], 'hr.report.view'],
['GET', '/hr/reports/loan-summary', 'HR\Controllers\HrReportController@loanSummary', ['auth'], 'hr.report.view'], ['GET', '/hr/reports/loan-summary', 'HR\Controllers\HrReportController@loanSummary', ['auth'], 'hr.report.view'],
['GET', '/hr/reports/contract-expiry', 'HR\Controllers\HrReportController@contractExpiry', ['auth'], 'hr.report.view'], ['GET', '/hr/reports/contract-expiry', 'HR\Controllers\HrReportController@contractExpiry', ['auth'], 'hr.report.view'],
['GET', '/hr/reports/workforce-statement', 'HR\Controllers\HrReportController@workforceStatement',['auth'], 'hr.report.view'],
]; ];
<?php
declare(strict_types=1);
namespace App\Modules\HR\Services;
use App\Core\App;
final class BankTransferService
{
public static function generateTransferData(int $periodId): array
{
$db = App::getInstance()->db();
$period = $db->selectOne("SELECT * FROM hr_payroll_periods WHERE id = ?", [$periodId]);
if (!$period) {
return ['success' => false, 'error' => 'فترة الرواتب غير موجودة'];
}
$rows = $db->select(
"SELECT pr.net_salary, pr.employee_number,
ep.first_name_ar, ep.last_name_ar,
ep.national_id, ep.bank_name, ep.bank_account_number
FROM hr_payroll_runs pr
JOIN hr_employee_profiles ep ON ep.id = pr.employee_profile_id
WHERE pr.period_id = ? AND pr.status IN ('calculated', 'approved', 'paid')
AND ep.bank_account_number IS NOT NULL AND ep.bank_account_number != ''
AND ep.is_archived = 0
ORDER BY ep.first_name_ar ASC",
[$periodId]
);
if (empty($rows)) {
return ['success' => false, 'error' => 'لا يوجد موظفون لديهم حسابات بنكية لهذه الفترة'];
}
$transfers = [];
$totalAmount = '0.00';
foreach ($rows as $row) {
$name = trim(($row['first_name_ar'] ?? '') . ' ' . ($row['last_name_ar'] ?? ''));
$transfers[] = [
'employee_number' => $row['employee_number'],
'employee_name' => $name,
'national_id' => $row['national_id'] ?? '',
'bank_name' => $row['bank_name'] ?? 'بنك الاسكندرية',
'bank_account_number'=> $row['bank_account_number'],
'net_salary' => $row['net_salary'],
];
$totalAmount = bcadd($totalAmount, $row['net_salary'], 2);
}
return [
'success' => true,
'period' => $period,
'transfers' => $transfers,
'total_amount' => $totalAmount,
'count' => count($transfers),
];
}
public static function generateCsv(int $periodId): ?string
{
$result = self::generateTransferData($periodId);
if (!$result['success']) {
return null;
}
$lines = [];
$lines[] = implode(',', ['م', 'اسم الموظف', 'الرقم القومي', 'رقم الحساب', 'صافي الراتب']);
$i = 1;
foreach ($result['transfers'] as $t) {
$lines[] = implode(',', [
$i,
'"' . str_replace('"', '""', $t['employee_name']) . '"',
$t['national_id'],
$t['bank_account_number'],
$t['net_salary'],
]);
$i++;
}
$lines[] = implode(',', ['', '', '', 'الإجمالي', $result['total_amount']]);
return "\xEF\xBB\xBF" . implode("\r\n", $lines);
}
}
...@@ -15,12 +15,15 @@ use App\Modules\Workflow\Services\WorkflowEngine; ...@@ -15,12 +15,15 @@ use App\Modules\Workflow\Services\WorkflowEngine;
/** /**
* Leave Entitlement & Management Service * Leave Entitlement & Management Service
* *
* Egyptian Labour Law (Art. 47-54, 91): * Egyptian Labour Law 2025 (New Law):
* Annual: 21 days (<10y), 30 days (>=10y or age>=50), 45 days (hazardous) * Annual: 15 days (1st year), 21 days (after 1y), 30 days (>=10y or age>=50), 45 days (disability)
* Casual: 7 days/year, max 2 consecutive * Casual: 7 days/year, max 2 consecutive
* Sick: 75% first 3 months, 85% next 3, then unpaid * Sick: 75% first 3 months, 85% next 3, then unpaid
* Maternity: 90 days paid, max 3 career, termination protection * Maternity: 4 months (120 days) paid at 100% comprehensive salary, max 3 career
* Hajj: 30 days unpaid, once in career * Hajj: 30 days PAID, once in career, requires 5 years service
* Childcare: unpaid, max 3 career, 1y service, 24-month gap between uses (female)
* Paternity: 1 day paid, max 3 career (male)
* Exam: paid, actual days, requires 10-day advance schedule, not deducted from annual
* Carry-over: max 3 years accumulation, min 6 consecutive/year * Carry-over: max 3 years accumulation, min 6 consecutive/year
*/ */
final class LeaveService final class LeaveService
...@@ -30,9 +33,16 @@ final class LeaveService ...@@ -30,9 +33,16 @@ final class LeaveService
$db = App::getInstance()->db(); $db = App::getInstance()->db();
$hireDate = $profile['hire_date'] ?? null; $hireDate = $profile['hire_date'] ?? null;
// Load configurable tier values
$daysFirstYear = '15.0';
$daysUnder10 = '21.0'; $daysUnder10 = '21.0';
$daysOver10 = '30.0'; $daysOver10 = '30.0';
$daysDisability = '45.0';
$row = $db->selectOne("SELECT config_value FROM system_config WHERE config_key = ?", ['hr.leave.annual_first_year']);
if ($row && $row['config_value'] !== '') {
$daysFirstYear = number_format((float) $row['config_value'], 1, '.', '');
}
$row = $db->selectOne("SELECT config_value FROM system_config WHERE config_key = ?", ['hr.leave.annual_days_under_10y']); $row = $db->selectOne("SELECT config_value FROM system_config WHERE config_key = ?", ['hr.leave.annual_days_under_10y']);
if ($row && $row['config_value'] !== '') { if ($row && $row['config_value'] !== '') {
$daysUnder10 = number_format((float) $row['config_value'], 1, '.', ''); $daysUnder10 = number_format((float) $row['config_value'], 1, '.', '');
...@@ -41,19 +51,35 @@ final class LeaveService ...@@ -41,19 +51,35 @@ final class LeaveService
if ($row && $row['config_value'] !== '') { if ($row && $row['config_value'] !== '') {
$daysOver10 = number_format((float) $row['config_value'], 1, '.', ''); $daysOver10 = number_format((float) $row['config_value'], 1, '.', '');
} }
$row = $db->selectOne("SELECT config_value FROM system_config WHERE config_key = ?", ['hr.leave.annual_disability']);
if ($row && $row['config_value'] !== '') {
$daysDisability = number_format((float) $row['config_value'], 1, '.', '');
}
// Disability override — Art. 47 (2025 Law): 45 days regardless of tenure
if (!empty($profile['has_disability']) && (int) $profile['has_disability'] === 1) {
return $daysDisability;
}
if (!$hireDate) { if (!$hireDate) {
return $daysUnder10; return $daysFirstYear;
} }
$yearsOfService = (float) HrEmployeeProfile::getYearsOfService($hireDate); $yearsOfService = (float) HrEmployeeProfile::getYearsOfService($hireDate);
$age = self::calculateAge($profile['date_of_birth'] ?? ''); $age = self::calculateAge($profile['date_of_birth'] ?? '');
// Tier 3: 10+ years of service OR age 50+
if ($yearsOfService >= 10 || $age >= 50) { if ($yearsOfService >= 10 || $age >= 50) {
return $daysOver10; return $daysOver10;
} }
return $daysUnder10; // Tier 2: after completing 1 year
if ($yearsOfService >= 1) {
return $daysUnder10;
}
// Tier 1: first year of employment
return $daysFirstYear;
} }
public static function submitRequest(array $data): array public static function submitRequest(array $data): array
...@@ -126,7 +152,7 @@ final class LeaveService ...@@ -126,7 +152,7 @@ final class LeaveService
} }
} }
// Career limits (maternity, hajj) // Career limits (maternity, hajj, childcare, paternity)
if ($leaveType['career_max_times']) { if ($leaveType['career_max_times']) {
$careerCount = $db->selectOne( $careerCount = $db->selectOne(
"SELECT COUNT(*) as cnt FROM hr_leave_requests "SELECT COUNT(*) as cnt FROM hr_leave_requests
...@@ -138,6 +164,41 @@ final class LeaveService ...@@ -138,6 +164,41 @@ final class LeaveService
} }
} }
// Minimum service months (hajj = 60 months, childcare = 12 months)
if (!empty($leaveType['min_service_months']) && (int) $leaveType['min_service_months'] > 0) {
$serviceMonths = (int) (HrEmployeeProfile::getYearsOfService($profile->hire_date ?? '') * 12);
if ($serviceMonths < (int) $leaveType['min_service_months']) {
$requiredYears = number_format((int) $leaveType['min_service_months'] / 12, 1);
return ['success' => false, 'error' => 'يشترط مدة خدمة لا تقل عن ' . $requiredYears . ' سنة للحصول على هذه الإجازة'];
}
}
// Childcare: enforce 24-month gap between uses
$rules = !empty($leaveType['rules_json']) ? json_decode($leaveType['rules_json'], true) : [];
if (!empty($rules['min_gap_months_between_uses'])) {
$lastApproved = $db->selectOne(
"SELECT end_date FROM hr_leave_requests
WHERE employee_profile_id = ? AND leave_type_id = ? AND status = 'approved' AND is_archived = 0
ORDER BY end_date DESC LIMIT 1",
[$profileId, $leaveTypeId]
);
if ($lastApproved) {
$lastEnd = new \DateTime($lastApproved['end_date']);
$requestStart = new \DateTime($startDate);
$monthsDiff = (int) $lastEnd->diff($requestStart)->m + ((int) $lastEnd->diff($requestStart)->y * 12);
if ($monthsDiff < (int) $rules['min_gap_months_between_uses']) {
return ['success' => false, 'error' => 'يجب مرور ' . $rules['min_gap_months_between_uses'] . ' شهر على الأقل بين كل استخدام لهذا النوع من الإجازات'];
}
}
}
// Casual leave: max consecutive days validation
if ($leaveType['code'] === 'casual' && !empty($leaveType['max_consecutive_days'])) {
if (bccomp($totalDays, (string) $leaveType['max_consecutive_days'], 1) > 0) {
return ['success' => false, 'error' => 'الحد الأقصى للإجازة العارضة المتصلة ' . $leaveType['max_consecutive_days'] . ' يوم'];
}
}
$db->beginTransaction(); $db->beginTransaction();
try { try {
$requestId = $db->insert('hr_leave_requests', [ $requestId = $db->insert('hr_leave_requests', [
......
...@@ -312,33 +312,21 @@ final class PayrollCalculationService ...@@ -312,33 +312,21 @@ final class PayrollCalculationService
'sort_order' => $sortOrder, 'sort_order' => $sortOrder,
]; ];
// ── Step 8b: Regular Stamp Duty deduction ── // ── Step 8b: Solidarity Fund (صندوق التكافل) — % of gross ──
$regularStampRow = $db->selectOne("SELECT config_value FROM system_config WHERE config_key = ?", ['hr.deduction.regular_stamp_duty']); $solidarityRow = $db->selectOne("SELECT config_value FROM system_config WHERE config_key = ?", ['hr.deduction.solidarity_fund_rate']);
$regularStampDeduction = $regularStampRow ? $regularStampRow['config_value'] : '0.00'; $solidarityRate = ($solidarityRow && $solidarityRow['config_value'] !== '') ? $solidarityRow['config_value'] : '0.0025';
$solidarityDeduction = bcmul($grossEarnings, $solidarityRate, 2);
$sortOrder++; $sortOrder++;
$componentLog[] = [ $componentLog[] = [
'component_code' => 'REGULAR_STAMP_DED', 'component_code' => 'SOLIDARITY_FUND_DED',
'name_ar' => 'الدمغة العادية', 'name_ar' => 'مساهمة صندوق التكافل',
'type' => 'deduction', 'type' => 'deduction',
'amount' => $regularStampDeduction, 'amount' => $solidarityDeduction,
'sort_order' => $sortOrder, 'sort_order' => $sortOrder,
]; ];
// ── Step 8c: Additional Stamp Duty deduction ── // ── Step 8c: External Loan Deduction (financing companies & bank loans) ──
$additionalStampRow = $db->selectOne("SELECT config_value FROM system_config WHERE config_key = ?", ['hr.deduction.additional_stamp_duty']);
$additionalStampDeduction = $additionalStampRow ? $additionalStampRow['config_value'] : '0.00';
$sortOrder++;
$componentLog[] = [
'component_code' => 'ADDITIONAL_STAMP_DED',
'name_ar' => 'الدمغة الإضافية',
'type' => 'deduction',
'amount' => $additionalStampDeduction,
'sort_order' => $sortOrder,
];
// ── Step 8d: External Loan Deduction (financing companies & bank loans) ──
$externalLoanRow = $db->selectOne( $externalLoanRow = $db->selectOne(
"SELECT COALESCE(SUM(monthly_salary_impact), 0) AS total_external FROM hr_employee_loans WHERE employee_profile_id = ? AND loan_type IN ('financing_company', 'bank_loan') AND status IN ('approved', 'disbursed', 'repaying') AND is_archived = 0", "SELECT COALESCE(SUM(monthly_salary_impact), 0) AS total_external FROM hr_employee_loans WHERE employee_profile_id = ? AND loan_type IN ('financing_company', 'bank_loan') AND status IN ('approved', 'disbursed', 'repaying') AND is_archived = 0",
[$profileId] [$profileId]
...@@ -354,13 +342,13 @@ final class PayrollCalculationService ...@@ -354,13 +342,13 @@ final class PayrollCalculationService
'sort_order' => $sortOrder, 'sort_order' => $sortOrder,
]; ];
// ── Step 9: Net salary ── // ── Step 9: Net before stamp duty ──
$otherDeductions = bcadd( $otherDeductions = bcadd(
bcadd(bcadd($martyrsFundDeduction, $regularStampDeduction, 2), $additionalStampDeduction, 2), bcadd($martyrsFundDeduction, $solidarityDeduction, 2),
$externalLoanDeduction, $externalLoanDeduction,
2 2
); );
$totalDeductions = bcadd( $totalDeductionsBeforeStamp = bcadd(
bcadd( bcadd(
bcadd(bcadd(bcadd($insuranceEmployee, $taxAmount, 2), $loanDeduction, 2), $penaltyDeduction, 2), bcadd(bcadd(bcadd($insuranceEmployee, $taxAmount, 2), $loanDeduction, 2), $penaltyDeduction, 2),
$absenceDeduction, $absenceDeduction,
...@@ -369,18 +357,44 @@ final class PayrollCalculationService ...@@ -369,18 +357,44 @@ final class PayrollCalculationService
$otherDeductions, $otherDeductions,
2 2
); );
$netSalary = bcsub($grossEarnings, $totalDeductions, 2); $netBeforeStamp = bcsub($grossEarnings, $totalDeductionsBeforeStamp, 2);
if (bccomp($netBeforeStamp, '0', 2) < 0) {
$netBeforeStamp = '0.00';
}
// ── Step 9a: Stamp Duty (دمغة عادية وإضافية) — % of net ──
$stampRow = $db->selectOne("SELECT config_value FROM system_config WHERE config_key = ?", ['hr.deduction.regular_stamp_duty']);
$stampRate = ($stampRow && $stampRow['config_value'] !== '' && $stampRow['config_value'] !== '0') ? $stampRow['config_value'] : '0.03';
$stampDeduction = bcmul($netBeforeStamp, $stampRate, 2);
$sortOrder++;
$componentLog[] = [
'component_code' => 'STAMP_DUTY_DED',
'name_ar' => 'دمغة عادية وإضافية',
'type' => 'deduction',
'amount' => $stampDeduction,
'sort_order' => $sortOrder,
];
// ── Step 9b: Final net salary ──
$totalDeductions = bcadd($totalDeductionsBeforeStamp, $stampDeduction, 2);
$netSalary = bcsub($netBeforeStamp, $stampDeduction, 2);
if (bccomp($netSalary, '0', 2) < 0) { if (bccomp($netSalary, '0', 2) < 0) {
$netSalary = '0.00'; $netSalary = '0.00';
} }
// Include stamp in other_deductions for storage
$otherDeductions = bcadd($otherDeductions, $stampDeduction, 2);
// ── Step 10: Persist records ── // ── Step 10: Persist records ──
$calculationJson = json_encode([ $calculationJson = json_encode([
'overtime' => $overtime, 'overtime' => $overtime,
'insurance' => $insurance, 'insurance' => $insurance,
'tax' => $tax, 'tax' => $tax,
'attendance' => $attendance, 'attendance' => $attendance,
'working_days' => $workingDays, 'working_days' => $workingDays,
'solidarity_fund' => ['rate' => $solidarityRate, 'amount' => $solidarityDeduction],
'stamp_duty' => ['rate' => $stampRate, 'amount' => $stampDeduction, 'base' => $netBeforeStamp],
], JSON_UNESCAPED_UNICODE); ], JSON_UNESCAPED_UNICODE);
$db->beginTransaction(); $db->beginTransaction();
......
<?php
declare(strict_types=1);
namespace App\Modules\HR\Services;
use App\Core\App;
final class WorkforceReportService
{
public static function getWorkforceStatement(): array
{
$db = App::getInstance()->db();
$departments = $db->select(
"SELECT d.id, d.name_ar, d.name_en, d.staffing_capacity,
COUNT(CASE WHEN ep.workforce_type = 'insured' AND ep.employment_status = 'active' THEN 1 END) AS insured_count,
COUNT(CASE WHEN ep.workforce_type = 'part_time' AND ep.employment_status = 'active' THEN 1 END) AS part_time_count,
COUNT(CASE WHEN ep.workforce_type NOT IN ('insured', 'part_time') AND ep.employment_status = 'active' THEN 1 END) AS other_count,
COUNT(CASE WHEN ep.employment_status = 'active' THEN 1 END) AS total_active
FROM hr_departments d
LEFT JOIN hr_employee_profiles ep ON ep.department_id = d.id AND ep.is_archived = 0
WHERE d.is_archived = 0 AND d.is_active = 1
GROUP BY d.id, d.name_ar, d.name_en, d.staffing_capacity
ORDER BY d.sort_order ASC, d.name_ar ASC",
[]
);
$totals = [
'insured_count' => 0,
'part_time_count' => 0,
'other_count' => 0,
'total_active' => 0,
'capacity' => 0,
'vacancies' => 0,
];
foreach ($departments as &$dept) {
$capacity = (int) ($dept['staffing_capacity'] ?? 0);
$active = (int) $dept['total_active'];
$dept['vacancies'] = max(0, $capacity - $active);
$totals['insured_count'] += (int) $dept['insured_count'];
$totals['part_time_count'] += (int) $dept['part_time_count'];
$totals['other_count'] += (int) $dept['other_count'];
$totals['total_active'] += $active;
$totals['capacity'] += $capacity;
$totals['vacancies'] += $dept['vacancies'];
}
unset($dept);
return [
'departments' => $departments,
'totals' => $totals,
'generated_at'=> date('Y-m-d H:i:s'),
];
}
}
<?php
/** @var \App\Modules\HR\Models\HrEmployeeProfile $profile */
/** @var string $job_title */
$__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: 40px;">
<h3 style="margin: 0;">شركة أوسي سبورت للخدمات الرياضية</h3>
<p style="margin: 5px 0; color: #666;">نادي النادي فرع شيراتون</p>
<hr style="border: 1px solid #333; margin: 15px 0;">
<h2 style="margin: 20px 0;">إقـــــرار إســــــــــتلام العمــــــــــل</h2>
</div>
<div style="font-size: 16px; line-height: 2.2;">
<p>
أقــــــــــر أنا / <strong style="border-bottom: 1px dotted #000; padding: 0 20px;"><?= e(trim(($profile->first_name_ar ?? '') . ' ' . ($profile->last_name_ar ?? ''))) ?></strong>
</p>
<p>
الرقم القومي / <strong style="border-bottom: 1px dotted #000; padding: 0 20px;"><?= e($profile->national_id ?? '......................') ?></strong>
</p>
<p>
العنــــــــــوان / <strong style="border-bottom: 1px dotted #000; padding: 0 20px;"><?= e($profile->address ?? '......................') ?></strong>
</p>
<p style="margin-top: 30px;">
بأنني قد أستلمت عملي بشركة أوسي سبورت للخدمات الرياضية ( نادي النادي فرع شيراتون )
</p>
<p>
وذلك لأداء مهام وظيفة / <strong style="border-bottom: 1px dotted #000; padding: 0 20px;"><?= e($job_title) ?></strong>
</p>
<p>
إعتبـــــــاراً من <strong style="border-bottom: 1px dotted #000; padding: 0 20px;"><?= e($profile->hire_date ?? '...../...../......') ?></strong>
</p>
</div>
<div style="margin-top: 80px; text-align: center;">
<p style="font-weight: bold;">المـــــقر بـــما فيــــــــــــه</p>
<br><br><br>
<p>التوقيع: ................................................</p>
</div>
</div>
<style>
@media print {
body { margin: 0; }
.print-page { padding: 20px !important; }
.no-print { display: none !important; }
}
</style>
<?php $__template->endSection(); ?>
<?php $__template->layout('Layout.main'); ?>
<?php $__template->section('title'); ?>كشف تحويل مرتبات بنك الاسكندرية<?php $__template->endSection(); ?>
<?php $__template->section('page_actions'); ?>
<a href="/hr/payroll/periods/<?= (int) $period['id'] ?>/bank-transfer/csv" class="btn btn-success">تصدير CSV</a>
<a href="/hr/payroll/periods/<?= (int) $period['id'] ?>" class="btn btn-secondary">رجوع</a>
<?php $__template->endSection(); ?>
<?php $__template->section('content'); ?>
<div class="card">
<div style="padding:16px;border-bottom:1px solid #E5E7EB;display:flex;justify-content:space-between;align-items:center;">
<div>
<h3 style="margin:0;font-size:16px;">كشف مرتبات بنك الاسكندرية — <?= e($period['year'] ?? '') ?>/<?= str_pad((string) ($period['month'] ?? ''), 2, '0', STR_PAD_LEFT) ?></h3>
<p style="margin:4px 0 0;font-size:13px;color:#6B7280;"><?= $count ?> موظف — إجمالي <?= money($total_amount) ?></p>
</div>
<button onclick="window.print()" class="btn btn-sm btn-outline-primary no-print">طباعة</button>
</div>
<div class="table-responsive">
<table class="data-table">
<thead>
<tr>
<th>م</th>
<th>اسم الموظف</th>
<th>الرقم القومي</th>
<th>رقم الحساب</th>
<th>صافي الراتب</th>
</tr>
</thead>
<tbody>
<?php $i = 1; foreach ($transfers as $t): ?>
<tr>
<td><?= $i++ ?></td>
<td style="font-weight:600;"><?= e($t['employee_name']) ?></td>
<td style="font-family:monospace;direction:ltr;"><?= e($t['national_id']) ?></td>
<td style="font-family:monospace;direction:ltr;"><?= e($t['bank_account_number']) ?></td>
<td style="font-weight:700;"><?= money($t['net_salary']) ?></td>
</tr>
<?php endforeach; ?>
<tr style="font-weight:700;background:#F3F4F6;">
<td colspan="4">الإجمالي</td>
<td><?= money($total_amount) ?></td>
</tr>
</tbody>
</table>
</div>
</div>
<?php $__template->endSection(); ?>
<?php $__template->layout('Layout.main'); ?>
<?php $__template->section('title'); ?>بيان القوة الفعلية<?php $__template->endSection(); ?>
<?php $__template->section('page_actions'); ?><a href="/hr/reports" class="btn btn-secondary">رجوع</a><?php $__template->endSection(); ?>
<?php $__template->section('content'); ?>
<div style="display:grid;grid-template-columns:repeat(auto-fit,minmax(160px,1fr));gap:12px;margin-bottom:20px;">
<div class="card" style="padding:16px;text-align:center;"><div style="font-size:13px;color:#6B7280;">إجمالي العاملين</div><div style="font-size:24px;font-weight:700;"><?= (int) ($totals['total_active'] ?? 0) ?></div></div>
<div class="card" style="padding:16px;text-align:center;"><div style="font-size:13px;color:#6B7280;">مؤمن عليهم</div><div style="font-size:24px;font-weight:700;color:#059669;"><?= (int) ($totals['insured_count'] ?? 0) ?></div></div>
<div class="card" style="padding:16px;text-align:center;"><div style="font-size:13px;color:#6B7280;">غير متفرغ</div><div style="font-size:24px;font-weight:700;color:#D97706;"><?= (int) ($totals['part_time_count'] ?? 0) ?></div></div>
<div class="card" style="padding:16px;text-align:center;"><div style="font-size:13px;color:#6B7280;">الشواغر</div><div style="font-size:24px;font-weight:700;color:#DC2626;"><?= (int) ($totals['vacancies'] ?? 0) ?></div></div>
</div>
<div class="card">
<div style="padding:16px;border-bottom:1px solid #E5E7EB;display:flex;justify-content:space-between;align-items:center;">
<h3 style="margin:0;font-size:16px;">بيان القوة الفعلية — <?= arabic_date(date('Y-m-d')) ?></h3>
<button onclick="window.print()" class="btn btn-sm btn-outline-primary no-print">طباعة</button>
</div>
<div class="table-responsive">
<table class="data-table">
<thead>
<tr>
<th>الإدارة / القسم</th>
<th>مؤمن عليهم</th>
<th>غير متفرغ</th>
<th>أخرى</th>
<th>إجمالي العاملين</th>
<th>القدرة الاستيعابية</th>
<th>الشواغر</th>
</tr>
</thead>
<tbody>
<?php if (empty($departments)): ?>
<tr><td colspan="7" style="text-align:center;padding:40px;color:#9CA3AF;">لا توجد بيانات</td></tr>
<?php else: ?>
<?php foreach ($departments as $d): ?>
<tr>
<td style="font-weight:600;"><?= e($d['name_ar'] ?? '') ?></td>
<td><?= (int) $d['insured_count'] ?></td>
<td><?= (int) $d['part_time_count'] ?></td>
<td><?= (int) $d['other_count'] ?></td>
<td style="font-weight:700;"><?= (int) $d['total_active'] ?></td>
<td><?= (int) ($d['staffing_capacity'] ?? 0) ?></td>
<td style="color:<?= (int) $d['vacancies'] > 0 ? '#DC2626' : '#059669' ?>;"><?= (int) $d['vacancies'] ?></td>
</tr>
<?php endforeach; ?>
<tr style="font-weight:700;background:#F3F4F6;">
<td>الإجمالي</td>
<td><?= (int) ($totals['insured_count'] ?? 0) ?></td>
<td><?= (int) ($totals['part_time_count'] ?? 0) ?></td>
<td><?= (int) ($totals['other_count'] ?? 0) ?></td>
<td><?= (int) ($totals['total_active'] ?? 0) ?></td>
<td><?= (int) ($totals['capacity'] ?? 0) ?></td>
<td style="color:#DC2626;"><?= (int) ($totals['vacancies'] ?? 0) ?></td>
</tr>
<?php endif; ?>
</tbody>
</table>
</div>
</div>
<?php $__template->endSection(); ?>
<?php
declare(strict_types=1);
use App\Core\Database;
return function (Database $db): void {
// ────────────────────────────────────────────────────────────
// 2025 Labour Law — Leave Type Updates
// ────────────────────────────────────────────────────────────
// 1A. Annual leave: set default_days_per_year to NULL (dynamic by tenure)
// and update rules_json with tier logic
$db->query(
"UPDATE hr_leave_types SET
default_days_per_year = NULL,
rules_json = ?,
legal_reference = 'Art. 47-50 (2025 Law)'
WHERE code = 'annual' AND is_archived = 0",
[json_encode([
'tiers' => [
['min_years' => 0, 'max_years' => 1, 'days' => 15, 'label' => 'السنة الأولى'],
['min_years' => 1, 'max_years' => 10, 'days' => 21, 'label' => 'بعد سنة'],
['min_years' => 10, 'max_years' => null, 'days' => 30, 'label' => 'بعد 10 سنوات'],
],
'age_50_plus_days' => 30,
'disability_days' => 45,
'entitled_from_day_1' => true,
'usable_after_months' => 6,
], JSON_UNESCAPED_UNICODE)]
);
// 1B. Casual leave: max 2 consecutive days
$db->query(
"UPDATE hr_leave_types SET max_consecutive_days = 2 WHERE code = 'casual' AND is_archived = 0",
[]
);
// 1C. Maternity: 120 days (4 months)
$db->query(
"UPDATE hr_leave_types SET
default_days_per_year = 120.0,
max_days_per_year = 120.0,
legal_reference = 'Art. 50 (2025 Law)',
rules_json = ?
WHERE code = 'maternity' AND is_archived = 0",
[json_encode([
'pre_delivery_days' => 45,
'pay_basis' => 'comprehensive_salary',
'termination_protection' => true,
], JSON_UNESCAPED_UNICODE)]
);
// 1D. Hajj: now PAID, requires 5 years service
$db->query(
"UPDATE hr_leave_types SET
is_paid = 1,
pay_percentage = 100.00,
min_service_months = 60,
deduct_from_salary = 0,
legal_reference = 'Art. 53 (2025 Law)',
rules_json = ?
WHERE code = 'hajj' AND is_archived = 0",
[json_encode([
'not_deducted_from_annual' => true,
], JSON_UNESCAPED_UNICODE)]
);
// 1E. Childcare leave (NEW)
$exists = $db->selectOne(
"SELECT 1 FROM hr_leave_types WHERE code = 'childcare' LIMIT 1",
[]
);
if (!$exists) {
$db->insert('hr_leave_types', [
'code' => 'childcare',
'name_ar' => 'إجازة رعاية الطفل',
'name_en' => 'Childcare Leave',
'category' => 'unpaid',
'default_days_per_year' => null,
'max_days_per_year' => 730.0,
'is_paid' => 0,
'pay_percentage' => 0.00,
'requires_approval' => 1,
'requires_attachment' => 0,
'min_days_per_request' => null,
'max_days_per_request' => null,
'max_consecutive_days' => null,
'max_per_occurrence' => null,
'max_times_in_career' => null,
'min_service_months' => 12,
'advance_notice_days' => 30,
'gender_restriction' => 'female',
'carry_over_allowed' => 0,
'is_accumulative' => 0,
'career_max_times' => 3,
'career_max_days' => null,
'deduct_from_salary' => 0,
'legal_reference' => 'Art. 53 (2025 Law)',
'rules_json' => json_encode([
'min_establishment_workers' => 50,
'min_gap_months_between_uses' => 24,
], JSON_UNESCAPED_UNICODE),
'is_active' => 1,
'sort_order' => 11,
'created_at' => date('Y-m-d H:i:s'),
'updated_at' => date('Y-m-d H:i:s'),
]);
}
// 1F. Paternity leave (NEW)
$exists = $db->selectOne(
"SELECT 1 FROM hr_leave_types WHERE code = 'paternity' LIMIT 1",
[]
);
if (!$exists) {
$db->insert('hr_leave_types', [
'code' => 'paternity',
'name_ar' => 'إجازة أبوة',
'name_en' => 'Paternity Leave',
'category' => 'special',
'default_days_per_year' => 1.0,
'max_days_per_year' => 1.0,
'is_paid' => 1,
'pay_percentage' => 100.00,
'requires_approval' => 1,
'requires_attachment' => 1,
'min_days_per_request' => null,
'max_days_per_request' => null,
'max_consecutive_days' => 1,
'max_per_occurrence' => 1,
'max_times_in_career' => null,
'min_service_months' => 0,
'advance_notice_days' => 0,
'gender_restriction' => 'male',
'carry_over_allowed' => 0,
'is_accumulative' => 0,
'career_max_times' => 3,
'career_max_days' => 3.0,
'deduct_from_salary' => 0,
'legal_reference' => 'Art. 52 (2025 Law)',
'rules_json' => json_encode([
'on_child_birthday' => true,
], JSON_UNESCAPED_UNICODE),
'is_active' => 1,
'sort_order' => 12,
'created_at' => date('Y-m-d H:i:s'),
'updated_at' => date('Y-m-d H:i:s'),
]);
}
// 1G. Exam leave (NEW)
$exists = $db->selectOne(
"SELECT 1 FROM hr_leave_types WHERE code = 'exam' LIMIT 1",
[]
);
if (!$exists) {
$db->insert('hr_leave_types', [
'code' => 'exam',
'name_ar' => 'إجازة امتحانات',
'name_en' => 'Exam Leave',
'category' => 'special',
'default_days_per_year' => null,
'max_days_per_year' => null,
'is_paid' => 1,
'pay_percentage' => 100.00,
'requires_approval' => 1,
'requires_attachment' => 1,
'min_days_per_request' => null,
'max_days_per_request' => null,
'max_consecutive_days' => null,
'max_per_occurrence' => null,
'max_times_in_career' => null,
'min_service_months' => 0,
'advance_notice_days' => 10,
'gender_restriction' => null,
'carry_over_allowed' => 0,
'is_accumulative' => 0,
'career_max_times' => null,
'career_max_days' => null,
'deduct_from_salary' => 0,
'legal_reference' => '2025 Law',
'rules_json' => json_encode([
'not_deducted_from_annual' => true,
'requires_exam_schedule' => true,
'actual_exam_days_only' => true,
], JSON_UNESCAPED_UNICODE),
'is_active' => 1,
'sort_order' => 13,
'created_at' => date('Y-m-d H:i:s'),
'updated_at' => date('Y-m-d H:i:s'),
]);
}
// ────────────────────────────────────────────────────────────
// System Config Updates for 2025 Law + Payroll Alignment
// ────────────────────────────────────────────────────────────
// Update maternity days
$db->query(
"UPDATE system_config SET config_value = '120' WHERE config_key = 'hr.leave.maternity_days'",
[]
);
// Add first-year annual days config
$exists = $db->selectOne(
"SELECT 1 FROM system_config WHERE config_key = 'hr.leave.annual_first_year' LIMIT 1",
[]
);
if (!$exists) {
$db->insert('system_config', [
'config_key' => 'hr.leave.annual_first_year',
'config_value' => '15',
'config_type' => 'integer',
'group_name' => 'hr',
'description_ar' => 'أيام الإجازة السنوية للسنة الأولى',
'description_en' => 'Annual leave days for first year',
'is_editable' => 1,
'created_at' => date('Y-m-d H:i:s'),
'updated_at' => date('Y-m-d H:i:s'),
]);
}
// Add disability annual days config
$exists = $db->selectOne(
"SELECT 1 FROM system_config WHERE config_key = 'hr.leave.annual_disability' LIMIT 1",
[]
);
if (!$exists) {
$db->insert('system_config', [
'config_key' => 'hr.leave.annual_disability',
'config_value' => '45',
'config_type' => 'integer',
'group_name' => 'hr',
'description_ar' => 'أيام الإجازة السنوية لذوي الإعاقة',
'description_en' => 'Annual leave days for employees with disability',
'is_editable' => 1,
'created_at' => date('Y-m-d H:i:s'),
'updated_at' => date('Y-m-d H:i:s'),
]);
}
// Payroll: stamp duty (3% of net)
$db->query(
"UPDATE system_config SET config_value = '0.03' WHERE config_key = 'hr.deduction.regular_stamp_duty'",
[]
);
// Solidarity fund rate (2.5‰ = 0.0025)
$exists = $db->selectOne(
"SELECT 1 FROM system_config WHERE config_key = 'hr.deduction.solidarity_fund_rate' LIMIT 1",
[]
);
if (!$exists) {
$db->insert('system_config', [
'config_key' => 'hr.deduction.solidarity_fund_rate',
'config_value' => '0.0025',
'config_type' => 'float',
'group_name' => 'hr',
'description_ar' => 'نسبة مساهمة صندوق التكافل الاجتماعي',
'description_en' => 'Solidarity fund contribution rate',
'is_editable' => 1,
'created_at' => date('Y-m-d H:i:s'),
'updated_at' => date('Y-m-d H:i:s'),
]);
}
// Emergency fund rate (1% of total basic salaries)
$exists = $db->selectOne(
"SELECT 1 FROM system_config WHERE config_key = 'hr.deduction.emergency_fund_rate' LIMIT 1",
[]
);
if (!$exists) {
$db->insert('system_config', [
'config_key' => 'hr.deduction.emergency_fund_rate',
'config_value' => '0.01',
'config_type' => 'float',
'group_name' => 'hr',
'description_ar' => 'نسبة صندوق الطوارئ',
'description_en' => 'Emergency fund rate',
'is_editable' => 1,
'created_at' => date('Y-m-d H:i:s'),
'updated_at' => date('Y-m-d H:i:s'),
]);
}
// VAT rate on payroll invoice
$exists = $db->selectOne(
"SELECT 1 FROM system_config WHERE config_key = 'hr.payroll.vat_rate' LIMIT 1",
[]
);
if (!$exists) {
$db->insert('system_config', [
'config_key' => 'hr.payroll.vat_rate',
'config_value' => '0.14',
'config_type' => 'float',
'group_name' => 'hr',
'description_ar' => 'نسبة ضريبة القيمة المضافة على فاتورة الرواتب',
'description_en' => 'VAT rate on payroll invoice',
'is_editable' => 1,
'created_at' => date('Y-m-d H:i:s'),
'updated_at' => date('Y-m-d H:i:s'),
]);
}
// ────────────────────────────────────────────────────────────
// Employee Profile: add variable_salary and total_allowances
// ────────────────────────────────────────────────────────────
$colExists = $db->selectOne(
"SELECT 1 FROM information_schema.columns
WHERE table_schema = DATABASE() AND table_name = 'hr_employee_profiles' AND column_name = 'variable_salary'",
[]
);
if (!$colExists) {
$db->raw("ALTER TABLE hr_employee_profiles ADD COLUMN variable_salary DECIMAL(15,2) NOT NULL DEFAULT 0.00 AFTER basic_salary");
}
$colExists = $db->selectOne(
"SELECT 1 FROM information_schema.columns
WHERE table_schema = DATABASE() AND table_name = 'hr_employee_profiles' AND column_name = 'total_allowances'",
[]
);
if (!$colExists) {
$db->raw("ALTER TABLE hr_employee_profiles ADD COLUMN total_allowances DECIMAL(15,2) NOT NULL DEFAULT 0.00 AFTER variable_salary");
}
};
<?php
declare(strict_types=1);
use App\Core\Database;
return function (Database $db): void {
$departments = [
['code' => 'EXEC', 'name_ar' => 'الإدارة العليا', 'name_en' => 'Executive Management'],
['code' => 'TECH', 'name_ar' => 'المكتب الفني', 'name_en' => 'Technical Office'],
['code' => 'SPORT', 'name_ar' => 'ادارة النشاط الرياضي', 'name_en' => 'Sports Activities'],
['code' => 'PR', 'name_ar' => 'ادارة العلاقات العامة', 'name_en' => 'Public Relations'],
['code' => 'FIN', 'name_ar' => 'الادارة المالية', 'name_en' => 'Finance'],
['code' => 'STORE', 'name_ar' => 'ادارة المخازن', 'name_en' => 'Warehouse'],
['code' => 'HR', 'name_ar' => 'ادارة الموارد البشرية', 'name_en' => 'Human Resources'],
['code' => 'LEGAL', 'name_ar' => 'ادارة الشئون القانونية', 'name_en' => 'Legal Affairs'],
['code' => 'SUPER', 'name_ar' => 'ادارة الاشراف والمتابعة', 'name_en' => 'Supervision & Follow-up'],
['code' => 'MKTG', 'name_ar' => 'ادارة التسويق والمبيعات', 'name_en' => 'Marketing & Sales'],
['code' => 'MEMB', 'name_ar' => 'ادارة اشتراكات العضوية', 'name_en' => 'Membership Subscriptions'],
['code' => 'IT', 'name_ar' => 'ادارة النظم والمعلومات', 'name_en' => 'IT & Systems'],
['code' => 'MED', 'name_ar' => 'الادارة الطبية', 'name_en' => 'Medical'],
['code' => 'MOSQ', 'name_ar' => 'المسجد', 'name_en' => 'Mosque'],
];
foreach ($departments as $dept) {
$existing = $db->selectOne(
"SELECT id FROM hr_departments WHERE department_code = ? AND is_archived = 0",
[$dept['code']]
);
if (!$existing) {
$db->insert('hr_departments', [
'department_code' => $dept['code'],
'department_type' => 'edara',
'name_ar' => $dept['name_ar'],
'name_en' => $dept['name_en'],
'parent_id' => null,
'is_active' => 1,
'sort_order' => 0,
'created_at' => date('Y-m-d H:i:s'),
'updated_at' => date('Y-m-d H:i:s'),
]);
}
}
// Extract distinct job titles from the registry JSON
$jsonPath = __DIR__ . '/data/employees_registry.json';
if (!file_exists($jsonPath)) {
return;
}
$employees = json_decode(file_get_contents($jsonPath), true);
$titles = [];
foreach ($employees as $emp) {
$title = trim($emp['job_title'] ?? '');
if ($title && !in_array($title, $titles, true)) {
$titles[] = $title;
}
}
$codeCounter = 1;
foreach ($titles as $title) {
$existing = $db->selectOne(
"SELECT id FROM hr_job_titles WHERE name_ar = ? AND is_archived = 0",
[$title]
);
if (!$existing) {
$code = 'JOB' . str_pad((string) $codeCounter, 3, '0', STR_PAD_LEFT);
$db->insert('hr_job_titles', [
'title_code' => $code,
'name_ar' => $title,
'name_en' => null,
'is_active' => 1,
'created_at' => date('Y-m-d H:i:s'),
'updated_at' => date('Y-m-d H:i:s'),
]);
}
$codeCounter++;
}
};
<?php
declare(strict_types=1);
use App\Core\Database;
return function (Database $db): void {
$jsonPath = __DIR__ . '/data/employees_registry.json';
$bankPath = __DIR__ . '/data/bank_accounts.json';
if (!file_exists($jsonPath)) {
echo "ERROR: employees_registry.json not found\n";
return;
}
$employees = json_decode(file_get_contents($jsonPath), true);
$bankData = file_exists($bankPath) ? json_decode(file_get_contents($bankPath), true) : [];
// Index bank accounts by national_id
$bankByNid = [];
foreach ($bankData as $b) {
$nid = trim($b['national_id'] ?? '');
if ($nid) {
$bankByNid[$nid] = $b['bank_account'] ?? '';
}
}
// Cache department IDs by name_ar
$deptRows = $db->select("SELECT id, name_ar FROM hr_departments WHERE is_archived = 0", []);
$deptMap = [];
foreach ($deptRows as $row) {
$deptMap[$row['name_ar']] = (int) $row['id'];
}
// Cache job title IDs by name_ar
$titleRows = $db->select("SELECT id, name_ar FROM hr_job_titles WHERE is_archived = 0", []);
$titleMap = [];
foreach ($titleRows as $row) {
$titleMap[$row['name_ar']] = (int) $row['id'];
}
// Get next employee_number sequence
$lastNum = $db->selectOne(
"SELECT employee_number FROM hr_employee_profiles ORDER BY CAST(SUBSTRING(employee_number, 4) AS UNSIGNED) DESC LIMIT 1",
[]
);
$nextSeq = 1;
if ($lastNum && preg_match('/EMP(\d+)/', $lastNum['employee_number'], $m)) {
$nextSeq = (int) $m[1] + 1;
}
$imported = 0;
$skipped = 0;
foreach ($employees as $emp) {
$nationalId = trim($emp['national_id'] ?? '');
// Skip employees without hire date (ministry secondees without data)
if (empty($emp['hire_date']) || $emp['hire_date'] === 'وزارة') {
$skipped++;
continue;
}
// Skip if already exists by national_id or employee_code
if ($nationalId) {
$exists = $db->selectOne(
"SELECT id FROM hr_employee_profiles WHERE national_id = ? AND is_archived = 0",
[$nationalId]
);
if ($exists) {
$skipped++;
continue;
}
}
// Parse name (full name in one field)
$fullName = trim($emp['name']);
$nameParts = preg_split('/\s+/', $fullName, 2);
$firstName = $nameParts[0] ?? $fullName;
$lastName = $nameParts[1] ?? '';
// Clean leading title prefixes (ك/, ك /)
$firstName = preg_replace('/^ك\s*\/\s*/', '', $firstName);
$fullName = preg_replace('/^ك\s*\/\s*/', '', $fullName);
$nameParts = preg_split('/\s+/', trim($fullName), 2);
$firstName = $nameParts[0] ?? $fullName;
$lastName = $nameParts[1] ?? '';
// Department lookup
$deptId = $deptMap[$emp['department']] ?? null;
// Job title lookup
$jobTitleId = $titleMap[$emp['job_title']] ?? null;
// Employee number
$employeeNumber = 'EMP' . str_pad((string) $nextSeq, 4, '0', STR_PAD_LEFT);
$nextSeq++;
// Workforce type mapping
$workforceType = match ($emp['workforce_type']) {
'insured' => 'insured',
'part_time' => 'part_time',
'seconded' => 'part_time',
default => 'insured',
};
// Bank info
$bankAccount = $bankByNid[$nationalId] ?? '';
// Calculate insurable salary (same as basic for insured, 0 for others)
$salary = (float) ($emp['salary'] ?? 0);
$insurableSalary = ($workforceType === 'insured' && $salary > 0) ? $salary : 0.00;
$hireDate = $emp['hire_date'];
if (strlen($hireDate) > 10) {
$hireDate = substr($hireDate, 0, 10);
}
$insuranceDate = $emp['insurance_date'] ?? '';
if (strlen($insuranceDate) > 10) {
$insuranceDate = substr($insuranceDate, 0, 10);
}
$dob = $emp['date_of_birth'] ?? '';
if (strlen($dob) > 10) {
$dob = substr($dob, 0, 10);
}
$gradYear = $emp['graduation_year'] ?? null;
if ($gradYear && (int) $gradYear > 2030) {
$gradYear = null;
}
$data = [
'employee_id' => 0,
'employee_number' => $employeeNumber,
'fingerprint_code' => $emp['employee_code'] ?: null,
'first_name_ar' => $firstName,
'last_name_ar' => $lastName,
'phone' => $emp['phone'] ?: null,
'national_id' => $nationalId ?: null,
'date_of_birth' => $dob ?: null,
'gender' => $emp['gender'],
'nationality' => 'egyptian',
'education_qualification' => $emp['qualification'] ?: null,
'education_graduation_year' => $gradYear ? (int) $gradYear : null,
'education_university' => $emp['university'] ?: null,
'education_grade' => $emp['grade'] ?: null,
'address' => $emp['address'] ?: null,
'department_id' => $deptId,
'job_title_id' => $jobTitleId,
'hire_date' => $hireDate,
'employment_type' => 'full_time',
'workforce_type' => $workforceType,
'employment_status' => 'active',
'basic_salary' => number_format($salary, 2, '.', ''),
'insurable_salary' => number_format($insurableSalary, 2, '.', ''),
'bank_name' => $bankAccount ? 'بنك الاسكندرية' : null,
'bank_account_number' => $bankAccount ?: null,
'insurance_number' => $emp['insurance_number'] ?: null,
'insurance_start_date' => $insuranceDate ?: null,
'military_status' => $emp['military_status'] ?: null,
'has_disability' => 0,
'created_at' => date('Y-m-d H:i:s'),
'updated_at' => date('Y-m-d H:i:s'),
];
$db->insert('hr_employee_profiles', $data);
$imported++;
}
echo "Employee import complete: {$imported} imported, {$skipped} skipped\n";
};
<?php
declare(strict_types=1);
use App\Core\Database;
return function (Database $db): void {
// Seed the standard 10-criteria performance evaluation template
// from the club's official evaluation form (تعديل تقييم الاداء)
$criteriaJson = json_encode([
'criteria' => [
['id' => 1, 'name_ar' => 'الإلمام بالمهام الوظيفية وسرعة ودقة إنجاز الأعمال', 'max_score' => 10],
['id' => 2, 'name_ar' => 'المشاركة والعمل ضمن فريق واحترام أوامر الرؤساء', 'max_score' => 10],
['id' => 3, 'name_ar' => 'القدرة على تحديد خطوط العمل والبرنامج الزمني', 'max_score' => 10],
['id' => 4, 'name_ar' => 'الحفاظ على المظهر العام والالتزام بمكان العمل', 'max_score' => 10],
['id' => 5, 'name_ar' => 'تحمل ضغط العمل في الأوقات الرسمية والإضافية', 'max_score' => 10],
['id' => 6, 'name_ar' => 'القدرة على نقل الخبرة في مجال التخصص', 'max_score' => 10],
['id' => 7, 'name_ar' => 'الرغبة في التعلم والتطوير وطرح أفكار جديدة', 'max_score' => 10],
['id' => 8, 'name_ar' => 'مدى الالتزام بتطبيق اللوائح والقوانين والأوامر الإدارية', 'max_score' => 10],
['id' => 9, 'name_ar' => 'القدرة على التواصل مع الآخرين (الموظفين – الأعضاء)', 'max_score' => 10],
['id' => 10, 'name_ar' => 'القدرة على العمل بدون إشراف وتحمل مسؤولية أعلى', 'max_score' => 10],
],
'total_max_score' => 100,
'rating_labels' => [
['min' => 0, 'max' => 49, 'label_ar' => 'ضعيف', 'label_en' => 'Weak'],
['min' => 50, 'max' => 64, 'label_ar' => 'مقبول', 'label_en' => 'Acceptable'],
['min' => 65, 'max' => 79, 'label_ar' => 'جيد', 'label_en' => 'Good'],
['min' => 80, 'max' => 89, 'label_ar' => 'جيد جداً', 'label_en' => 'Very Good'],
['min' => 90, 'max' => 100, 'label_ar' => 'ممتاز', 'label_en' => 'Excellent'],
],
'evaluators' => [
['role' => 'direct_manager', 'label_ar' => 'المدير المباشر'],
['role' => 'general_supervisor', 'label_ar' => 'المشرف العام'],
],
'signatories' => [
['role' => 'employee', 'label_ar' => 'توقيع الموظف'],
['role' => 'hr_manager', 'label_ar' => 'مدير الموارد البشرية'],
['role' => 'general_supervisor', 'label_ar' => 'المشرف العام'],
['role' => 'board_chairman', 'label_ar' => 'رئيس مجلس الإدارة'],
['role' => 'executive_director', 'label_ar' => 'المدير التنفيذي'],
],
], JSON_UNESCAPED_UNICODE);
$exists = $db->selectOne(
"SELECT 1 FROM system_config WHERE config_key = 'hr.performance.criteria_template' LIMIT 1",
[]
);
if (!$exists) {
$db->insert('system_config', [
'config_key' => 'hr.performance.criteria_template',
'config_value' => $criteriaJson,
'config_type' => 'json',
'group_name' => 'hr',
'description_ar' => 'قالب معايير تقييم الأداء (10 معايير × 10 درجات)',
'description_en' => 'Performance evaluation criteria template',
'is_editable' => 1,
'created_at' => date('Y-m-d H:i:s'),
'updated_at' => date('Y-m-d H:i:s'),
]);
} else {
$db->query(
"UPDATE system_config SET config_value = ?, updated_at = NOW() WHERE config_key = 'hr.performance.criteria_template'",
[$criteriaJson]
);
}
};
[
{
"name": "مجدي فتحي محمد الخمري",
"national_id": "28103271700511",
"bank_account": "165006228001"
},
{
"name": "محمد سعد محمد وهبه",
"national_id": "28806272102457",
"bank_account": "165012918001"
},
{
"name": "محمد عبد الحميد محمد جويده",
"national_id": "29108131501417",
"bank_account": "165010437001"
},
{
"name": "شيماء حمدى طاهر محمد",
"national_id": "30004060103224",
"bank_account": "165010265001"
},
{
"name": "هانى محمد ابراهيم محمد",
"national_id": "28502080103671",
"bank_account": "165010435001"
},
{
"name": "مصطفى سامى احمد محمد",
"national_id": "29912280100658",
"bank_account": "165010251001"
},
{
"name": "اسامه هاشم حسن محمد",
"national_id": "26901290101111",
"bank_account": "148009547001"
},
{
"name": "ريهام طلعت محمود عبدالرحيم",
"national_id": "28803300102043",
"bank_account": "165010353001"
},
{
"name": "محمود محمد اشرف محمود",
"national_id": "27412060104679",
"bank_account": "165010434001"
},
{
"name": "محمد يحيى عتريس محمد",
"national_id": "28408280102193",
"bank_account": "165010249001"
},
{
"name": "زياد فرج احمد الشحات",
"national_id": "29909150108311",
"bank_account": "165010255001"
},
{
"name": "هالة كارم محمد الحسيني",
"national_id": "28109081700122",
"bank_account": "165012921001"
},
{
"name": "مصطفي سيد علي عبدالقادر",
"national_id": "28708110102117",
"bank_account": "165012916001"
},
{
"name": "مصطفي خالد محمد علي",
"national_id": "30002150100078",
"bank_account": "165012919001"
},
{
"name": "ميادة محمد خضير حسن",
"national_id": "30005251401264",
"bank_account": "165012924001"
},
{
"name": "رشا بخيت عبد الشفاق فراج",
"national_id": "28109101302366",
"bank_account": "165012915001"
},
{
"name": "احمد حسين عبدالمنعم احمد",
"national_id": "28611230101013",
"bank_account": "165012908001"
},
{
"name": "اسلام محمد احمد محمد",
"national_id": "29011160100835",
"bank_account": "165012911001"
},
{
"name": "احمد عبد الله احمد بكير",
"national_id": "28204051701518",
"bank_account": "165012907001"
},
{
"name": "مراد طارق محمود صالح",
"national_id": "28109160102371",
"bank_account": "165012917001"
},
{
"name": "يوسف محمد مصطفي يوسف",
"national_id": "29911090103514",
"bank_account": "165012922001"
},
{
"name": "احمد أنور محمد محمد عبدالكريم",
"national_id": "29003010201673",
"bank_account": "165012906001"
},
{
"name": "هبه محمود محمود مرسي",
"national_id": "27808080100166",
"bank_account": "165010237002"
},
{
"name": "منار هشام محمد محمود",
"national_id": "30002122101042",
"bank_account": "165012923001"
},
{
"name": "أحمد نبيل محمود زاخر",
"national_id": "29211232400773",
"bank_account": "165012943001"
},
{
"name": "أحمد محمد سعد عبد العزيز",
"national_id": "29506030100697",
"bank_account": "165012939001"
},
{
"name": "أمل إبراهيم حسين بسيونى عيد",
"national_id": "28203121701123",
"bank_account": "165012938001"
},
{
"name": "اسماعيل محمد إبراهيم عمارة",
"national_id": "28401290102031",
"bank_account": "165010440001"
},
{
"name": "احمد إبراهيم الشحات عبد السميع",
"national_id": "28303151203572",
"bank_account": "165010350001"
},
{
"name": "اسلام محمد حسن عمر",
"national_id": "29812042100572",
"bank_account": "165012997001"
},
{
"name": "رؤى محمد حسن",
"national_id": "29406130100142",
"bank_account": "165010236001"
},
{
"name": "عمر محمد طاهر عبد الجليل",
"national_id": "29904040100976",
"bank_account": "165010259001"
},
{
"name": "محمد عبد المنعم محمود عبد اللاه",
"national_id": "28803201306656",
"bank_account": "165010257001"
},
{
"name": "اسلام محمد مصطفي يوسف",
"national_id": "29804090100271",
"bank_account": "165013055001"
}
]
\ No newline at end of file
[
{
"name": "امين محمد امين محمد عمر",
"job_title": "مستشار المجلس للشئون الرياضية",
"employee_code": "",
"workforce_type": "insured",
"hire_date": "2022-09-01",
"insurance_date": "2023-03-31",
"insurance_number": "23153514",
"qualification": "ليسانس حقوق",
"graduation_year": "2006",
"university": "جامعة عين شمس",
"grade": "جيد",
"date_of_birth": "1985-09-27",
"national_id": "28509271400972",
"military_status": "اعفاء نهائي",
"gender": "male",
"phone": "1093228884",
"address": "6أ ش باحثة البادية كورنيش النيل الساحل القاهرة",
"salary": 6900.0,
"department": "الإدارة العليا"
},
{
"name": "مجدي فتحي الخمري",
"job_title": "المشرف العام للنادي",
"employee_code": "1",
"workforce_type": "part_time",
"hire_date": "2022-06-01",
"insurance_date": "",
"insurance_number": "",
"qualification": "دكتوراة فى التربية الرياضية",
"graduation_year": "2015",
"university": "جامعة حلوان",
"grade": "",
"date_of_birth": "1981-03-27",
"national_id": "28103271700511",
"military_status": "",
"gender": "male",
"phone": "1006227278",
"address": "فيلا 15 مجاورة 10 التجمع الأول - القاهرة",
"salary": 28300.0,
"department": "الإدارة العليا"
},
{
"name": "محمد نبيل السيد",
"job_title": "مدير التشغيل",
"employee_code": "207",
"workforce_type": "part_time",
"hire_date": "2026-06-01",
"insurance_date": "",
"insurance_number": "146872",
"qualification": "بكالوريوس تربية رياضية",
"graduation_year": "2006",
"university": "جامعة حلوان",
"grade": "مقبول",
"date_of_birth": "1982-01-22",
"national_id": "28201228800431",
"military_status": "اعفاء نهائي",
"gender": "male",
"phone": "1028738698",
"address": "3ش محمد عبد السلام - الزهراء - مصر القديمة - القاهرة",
"salary": 20000.0,
"department": "الإدارة العليا"
},
{
"name": "محمد السيد عبد الفتاح",
"job_title": "مدير المكتب الفني",
"employee_code": "111",
"workforce_type": "seconded",
"hire_date": "وزارة",
"insurance_date": "",
"insurance_number": "",
"qualification": "بكالوريوس تجارة",
"graduation_year": "",
"university": "",
"grade": "",
"date_of_birth": "",
"national_id": "",
"military_status": "",
"gender": "male",
"phone": "",
"address": "",
"salary": 9500.0,
"department": "المكتب الفني"
},
{
"name": "ايمان احمد محمود منصور",
"job_title": "نائب مدير المكتب الفني",
"employee_code": "43",
"workforce_type": "part_time",
"hire_date": "2022-09-01",
"insurance_date": "",
"insurance_number": "51697561",
"qualification": "بكالوريوس تجارة",
"graduation_year": "1998",
"university": "جامعة عين شمس",
"grade": "مقبول",
"date_of_birth": "1976-11-04",
"national_id": "27611040101986",
"military_status": "",
"gender": "female",
"phone": "1126232782",
"address": "47أ عمارات ضباط الجيش مدينة نصر أول القاهرة",
"salary": 7180.0,
"department": "المكتب الفني"
},
{
"name": "رؤي محمد حسن",
"job_title": "موظف المكتب الفني",
"employee_code": "36",
"workforce_type": "insured",
"hire_date": "2022-10-01",
"insurance_date": "2023-01-31",
"insurance_number": "75471319",
"qualification": "بكالوريوس تجارة",
"graduation_year": "2017",
"university": "المعهد العالى للدراسات التعاونية والادارية",
"grade": "مقبول",
"date_of_birth": "1994-06-13",
"national_id": "29406130100142",
"military_status": "",
"gender": "female",
"phone": "1069955809",
"address": "مساكن العبد الجديد ب 120 م1 السلام القاهرة",
"salary": 7180.0,
"department": "المكتب الفني"
},
{
"name": "صفوت عبد العزيز حسن",
"job_title": "مندوب مراسلات",
"employee_code": "",
"workforce_type": "part_time",
"hire_date": "2026-05-01",
"insurance_date": "",
"insurance_number": "",
"qualification": "دبلوم صناعى",
"graduation_year": "2001",
"university": "",
"grade": "",
"date_of_birth": "1984-01-08",
"national_id": "28401081306931",
"military_status": "اعفاء نهائي",
"gender": "male",
"phone": "1273479974",
"address": "13حسن عمران - ثلاجة الزهراء - الحرفيين أول السلام - القاهرة",
"salary": 6000.0,
"department": "المكتب الفني"
},
{
"name": "ك/ محمد السعيد محمد",
"job_title": "نائب مدير النشاط الرياضي",
"employee_code": "110",
"workforce_type": "seconded",
"hire_date": "وزارة",
"insurance_date": "",
"insurance_number": "",
"qualification": "بكالوريوس تربية رياضية",
"graduation_year": "",
"university": "",
"grade": "",
"date_of_birth": "",
"national_id": "",
"military_status": "",
"gender": "male",
"phone": "",
"address": "",
"salary": 8000.0,
"department": "ادارة النشاط الرياضي"
},
{
"name": "ك / حمدي عبده حسن",
"job_title": "مدير صالة الجمباز",
"employee_code": "104",
"workforce_type": "seconded",
"hire_date": "وزارة",
"insurance_date": "",
"insurance_number": "",
"qualification": "بكالوريوس تربية رياضية",
"graduation_year": "",
"university": "",
"grade": "",
"date_of_birth": "",
"national_id": "",
"military_status": "",
"gender": "male",
"phone": "",
"address": "",
"salary": 5000.0,
"department": "ادارة النشاط الرياضي"
},
{
"name": "ك / محمد عبد الحميد محمد",
"job_title": "مشرف نشاط رياضي",
"employee_code": "11",
"workforce_type": "insured",
"hire_date": "2023-01-01",
"insurance_date": "2023-03-31",
"insurance_number": "69258007",
"qualification": "دبلوم صنايع",
"graduation_year": "2011",
"university": "",
"grade": "",
"date_of_birth": "1991-08-13",
"national_id": "29108131501417",
"military_status": "ادي الخدمة",
"gender": "male",
"phone": "1113433966",
"address": "131مدينة الابطال للقوات المسلحة حلوان القاهرة",
"salary": 7390.0,
"department": "ادارة النشاط الرياضي"
},
{
"name": "ك / أحمد نبيل محمود",
"job_title": "مشرف نشاط رياضي",
"employee_code": "10",
"workforce_type": "insured",
"hire_date": "2023-07-01",
"insurance_date": "2023-07-01",
"insurance_number": "83993542",
"qualification": "بكالوريوس نظم ومعلومات",
"graduation_year": "2018",
"university": "معهد الفراعنة العالى للحاسب الالى",
"grade": "جيد",
"date_of_birth": "1992-11-23",
"national_id": "29211232400773",
"military_status": "أدى الخدمة",
"gender": "male",
"phone": "1140962062",
"address": "الملكية القبلية خلف المساكن ملوى المنيا",
"salary": 7180.0,
"department": "ادارة النشاط الرياضي"
},
{
"name": "ك / عمر محمد طاهر",
"job_title": "مشرف نشاط رياضي",
"employee_code": "9",
"workforce_type": "insured",
"hire_date": "2022-10-01",
"insurance_date": "2023-01-31",
"insurance_number": "82531691",
"qualification": "بكالوريوس دراسات سياحية",
"graduation_year": "2021",
"university": "معهد الالسن العالي",
"grade": "مقبول",
"date_of_birth": "1999-04-04",
"national_id": "29904040100976",
"military_status": "اعفاء نهائي",
"gender": "male",
"phone": "1092522776",
"address": "31ش 7- عزبة الهجانة مدينة نصر اول القاهرة",
"salary": 7390.0,
"department": "ادارة النشاط الرياضي"
},
{
"name": "ك / منار هشام محمد محمود",
"job_title": "مشرف نشاط رياضي",
"employee_code": "92",
"workforce_type": "insured",
"hire_date": "2025-11-01",
"insurance_date": "2025-11-01",
"insurance_number": "87918558",
"qualification": "بكالوريوس تربية رياضية",
"graduation_year": "2023",
"university": "جامعة حلوان",
"grade": "مقبول",
"date_of_birth": "2000-02-12",
"national_id": "30002122101042",
"military_status": "",
"gender": "female",
"phone": "1220037716",
"address": "قرية السعودية - مركز العياط - الجيزة",
"salary": 6100.0,
"department": "ادارة النشاط الرياضي"
},
{
"name": "ك / محمد ماهر جابر احمد",
"job_title": "مشرف نشاط رياضي",
"employee_code": "94",
"workforce_type": "part_time",
"hire_date": "2025-11-01",
"insurance_date": "",
"insurance_number": "",
"qualification": "بكالوريوس تربية رياضية",
"graduation_year": "2015",
"university": "جامعة مدينة السادات",
"grade": "جيد",
"date_of_birth": "1993-10-25",
"national_id": "29310251700271",
"military_status": "أدى الخدمة",
"gender": "male",
"phone": "1064025798",
"address": "14ح عبد ربه - ش الزعيم غاندى - عين شمس - القاهرة",
"salary": 6100.0,
"department": "ادارة النشاط الرياضي"
},
{
"name": "ك / احمد حسين عبد المنعم",
"job_title": "مشرف بولينج",
"employee_code": "8",
"workforce_type": "insured",
"hire_date": "2025-01-01",
"insurance_date": "2025-01-01",
"insurance_number": "7123683",
"qualification": "بكالوريوس تجارة",
"graduation_year": "2010",
"university": "جامعة عين شمس",
"grade": "مقبول",
"date_of_birth": "1986-11-23",
"national_id": "28611230101013",
"military_status": "إعفاء مؤقت",
"gender": "male",
"phone": "1006471872",
"address": "58ب مج اول التجمع الاول القاهرة",
"salary": 6710.0,
"department": "ادارة النشاط الرياضي"
},
{
"name": "ك / هاني محمد إبراهيم محمد",
"job_title": "مشرف بولينج",
"employee_code": "34",
"workforce_type": "insured",
"hire_date": "2023-01-01",
"insurance_date": "2023-03-31",
"insurance_number": "21814974",
"qualification": "دبلوم صناعى",
"graduation_year": "2002",
"university": "مدرسة مدينة نصر الثانوية الصناعية",
"grade": "",
"date_of_birth": "1985-02-08",
"national_id": "28502080103671",
"military_status": "إعفاء نهائى",
"gender": "male",
"phone": "1023288481",
"address": "1230زهراء مدينة نصر مدينة نصر أول القاهرة",
"salary": 6900.0,
"department": "ادارة النشاط الرياضي"
},
{
"name": "ك / أسامة هاشم حسن",
"job_title": "مشرف بولينج",
"employee_code": "50",
"workforce_type": "insured",
"hire_date": "2022-06-01",
"insurance_date": "2023-03-31",
"insurance_number": "10694250",
"qualification": "دبلوم صناعى",
"graduation_year": "1987",
"university": "",
"grade": "",
"date_of_birth": "1969-01-29",
"national_id": "26901290101111",
"military_status": "إعفاء نهائى",
"gender": "male",
"phone": "1143285884",
"address": "مساكن الإيواء بلوك 3 مدخل 2 الشرابية القاهرة",
"salary": 6780.0,
"department": "ادارة النشاط الرياضي"
},
{
"name": "ك / مصطفى البدرى",
"job_title": "امين خزنة فرعي",
"employee_code": "113",
"workforce_type": "seconded",
"hire_date": "وزارة",
"insurance_date": "",
"insurance_number": "",
"qualification": "دبلوم صنايع",
"graduation_year": "",
"university": "",
"grade": "",
"date_of_birth": "",
"national_id": "",
"military_status": "",
"gender": "male",
"phone": "",
"address": "",
"salary": 4000.0,
"department": "ادارة النشاط الرياضي"
},
{
"name": "ك / عبد الرحمن البدرى",
"job_title": "مشرف بولينج",
"employee_code": "107",
"workforce_type": "seconded",
"hire_date": "وزارة",
"insurance_date": "",
"insurance_number": "",
"qualification": "دبلوم سياحة وفنادق",
"graduation_year": "",
"university": "",
"grade": "",
"date_of_birth": "",
"national_id": "",
"military_status": "",
"gender": "male",
"phone": "",
"address": "",
"salary": 4000.0,
"department": "ادارة النشاط الرياضي"
},
{
"name": "ك / احمد عبد الخالق اسماعيل",
"job_title": "مدير مجمع السباحة",
"employee_code": "204",
"workforce_type": "part_time",
"hire_date": "2026-04-18",
"insurance_date": "",
"insurance_number": "",
"qualification": "بكالوريوس تربية رياضية",
"graduation_year": "2006",
"university": "جامعة حلوان",
"grade": "مقبول",
"date_of_birth": "1977-11-12",
"national_id": "27711120102554",
"military_status": "أدى الخدمة",
"gender": "male",
"phone": "1007942318",
"address": "46ش16 - العزبة البحرية - حلوان - القاهرة",
"salary": 13500.0,
"department": "ادارة النشاط الرياضي"
},
{
"name": "ك / أحمد محمد سعد عبد العزيز",
"job_title": "مشرف حمام سباحة",
"employee_code": "66",
"workforce_type": "part_time",
"hire_date": "2024-06-01",
"insurance_date": "",
"insurance_number": "80273229",
"qualification": "ليسانس حقوق",
"graduation_year": "2021",
"university": "جامعة عين شمس",
"grade": "مقبول",
"date_of_birth": "1995-06-03",
"national_id": "29506030100697",
"military_status": "إعفاء نهائى",
"gender": "male",
"phone": "1122770659",
"address": "8إبراهيم صالح أمام إدارة التجنيد الزيتون القاهرة",
"salary": 7150.0,
"department": "ادارة النشاط الرياضي"
},
{
"name": "ك / مصطفى خالد محمد على",
"job_title": "مشرف حمام سباحة",
"employee_code": "97",
"workforce_type": "insured",
"hire_date": "2025-11-01",
"insurance_date": "2025-11-01",
"insurance_number": "76385535",
"qualification": "بكالوريوس تربية رياضية",
"graduation_year": "2023",
"university": "جامعة حلوان",
"grade": "جيد",
"date_of_birth": "2000-02-15",
"national_id": "30002150100078",
"military_status": "أدى الخدمة",
"gender": "male",
"phone": "1154998973",
"address": "ع81الجنوبى - التجمع الأول - القاهرة",
"salary": 6100.0,
"department": "ادارة النشاط الرياضي"
},
{
"name": "ك / عبد الله طارق محمد محمد",
"job_title": "مشرف حمام سباحة",
"employee_code": "218",
"workforce_type": "part_time",
"hire_date": "2026-07-20",
"insurance_date": "",
"insurance_number": "82407205",
"qualification": "بكالوريوس تربية رياضية",
"graduation_year": "2022",
"university": "جامعة حلوان",
"grade": "جيد",
"date_of_birth": "1999-07-19",
"national_id": "29907190103476",
"military_status": "أدى الخدمة",
"gender": "male",
"phone": "1023061346",
"address": "53ش محمد حسنين - عين شمس الغربية - المطرية - القاهرة",
"salary": 6500.0,
"department": "ادارة النشاط الرياضي"
},
{
"name": "نجلاء محمد السيد",
"job_title": "مدير إشتراكات أكاديميات",
"employee_code": "6",
"workforce_type": "part_time",
"hire_date": "2022-09-01",
"insurance_date": "",
"insurance_number": "",
"qualification": "ليسانس أداب تعليم مفتوح",
"graduation_year": "2016",
"university": "جامعة عين شمس",
"grade": "جيد",
"date_of_birth": "1977-09-19",
"national_id": "27709190101046",
"military_status": "",
"gender": "female",
"phone": "1012040776",
"address": "6أ ش أحمد حتاته سراى القبة الزيتون القاهرة",
"salary": 7180.0,
"department": "ادارة النشاط الرياضي"
},
{
"name": "محمود محمد اشرف محمود",
"job_title": "مسئول إشتراكات أكاديميات",
"employee_code": "30",
"workforce_type": "insured",
"hire_date": "2023-01-01",
"insurance_date": "2023-03-31",
"insurance_number": "14030749",
"qualification": "بكالوريوس تربية رياضية",
"graduation_year": "1997",
"university": "جامعة حلوان",
"grade": "جيد",
"date_of_birth": "1974-12-06",
"national_id": "27412060104679",
"military_status": "إعفاء نهائى",
"gender": "male",
"phone": "1001563387",
"address": "2ش إبراهيم العرابى النزهة الجديدة النزهة القاهرة",
"salary": 8600.0,
"department": "ادارة النشاط الرياضي"
},
{
"name": "اسلام محمد أحمد محمد",
"job_title": "مسئول إشتراكات أكاديميات",
"employee_code": "78",
"workforce_type": "insured",
"hire_date": "2025-01-01",
"insurance_date": "2025-06-01",
"insurance_number": "70043957",
"qualification": "بكالوريوس تجارة",
"graduation_year": "2012",
"university": "معهد الجزيرة العالي",
"grade": "جيد",
"date_of_birth": "1990-11-16",
"national_id": "29011160100835",
"military_status": "لم يصبه الدور",
"gender": "male",
"phone": "1019751231",
"address": "5ش عبده باشا عطفة اللبان العباسية الوايلي القاهرة",
"salary": 6380.0,
"department": "ادارة النشاط الرياضي"
},
{
"name": "رشا شريف مصطفى قاسم",
"job_title": "مدير العلاقات العامة",
"employee_code": "45",
"workforce_type": "part_time",
"hire_date": "2022-10-01",
"insurance_date": "",
"insurance_number": "7160916",
"qualification": "بكالوريوس خدمة إجتماعية",
"graduation_year": "19999",
"university": "المعهد العالى للخدمة الإجتماعية ببنها",
"grade": "جيد",
"date_of_birth": "1979-10-01",
"national_id": "27910011700541",
"military_status": "",
"gender": "female",
"phone": "1023818788",
"address": "120إمتداد رمسيس 2 مدينة نصر أول القاهرة",
"salary": 10110.0,
"department": "ادارة العلاقات العامة"
},
{
"name": "ولاء احمد",
"job_title": "مسئول علاقات عامة",
"employee_code": "116",
"workforce_type": "seconded",
"hire_date": "وزارة",
"insurance_date": "",
"insurance_number": "",
"qualification": "ليسانس أداب",
"graduation_year": "",
"university": "",
"grade": "",
"date_of_birth": "",
"national_id": "",
"military_status": "",
"gender": "male",
"phone": "",
"address": "",
"salary": 5000.0,
"department": "ادارة العلاقات العامة"
},
{
"name": "رشا بخيت عبد الشفاق",
"job_title": "مسئول علاقات عامة",
"employee_code": "82",
"workforce_type": "part_time",
"hire_date": "2025-03-01",
"insurance_date": "",
"insurance_number": "52700037",
"qualification": "بكالوريوس الادارة الصناعية",
"graduation_year": "2010",
"university": "جامعة عمالية",
"grade": "جيد",
"date_of_birth": "1981-09-10",
"national_id": "28109101302366",
"military_status": "",
"gender": "female",
"phone": "1002745371",
"address": "51عمارات صقر قريش النزهة القاهرة",
"salary": 6710.0,
"department": "ادارة العلاقات العامة"
},
{
"name": "شيرين فؤاد جمعه",
"job_title": "مسئول علاقات عامة",
"employee_code": "217",
"workforce_type": "part_time",
"hire_date": "2026-07-01",
"insurance_date": "",
"insurance_number": "77397704",
"qualification": "ليسانس أداب",
"graduation_year": "2003",
"university": "جامعة حلوان",
"grade": "مقبول",
"date_of_birth": "1980-12-08",
"national_id": "28012082100745",
"military_status": "",
"gender": "female",
"phone": "1064873666",
"address": "22إبن الرشيد - خلف مستشفى الرمد - الجيزة",
"salary": 8600.0,
"department": "ادارة العلاقات العامة"
},
{
"name": "هيثم حسين",
"job_title": "المدير المالي",
"employee_code": "115",
"workforce_type": "seconded",
"hire_date": "وزارة",
"insurance_date": "",
"insurance_number": "",
"qualification": "بكالوريوس تجارة",
"graduation_year": "",
"university": "",
"grade": "",
"date_of_birth": "",
"national_id": "",
"military_status": "",
"gender": "male",
"phone": "",
"address": "",
"salary": 7000.0,
"department": "الادارة المالية"
},
{
"name": "وليد محمد غندور",
"job_title": "مدير حسابات",
"employee_code": "211",
"workforce_type": "part_time",
"hire_date": "2026-06-01",
"insurance_date": "",
"insurance_number": "",
"qualification": "ليسانس أداب",
"graduation_year": "",
"university": "",
"grade": "",
"date_of_birth": "1985-06-28",
"national_id": "28506280100095",
"military_status": "",
"gender": "male",
"phone": "1092063094",
"address": "9عطفة المناخ - القلعة - الخليفة - القاهرة",
"salary": 11500.0,
"department": "الادارة المالية"
},
{
"name": "أحمد إبراهيم الشحات عبد السميع",
"job_title": "مشرف مالى",
"employee_code": "",
"workforce_type": "part_time",
"hire_date": "2022-11-01",
"insurance_date": "",
"insurance_number": "49829290",
"qualification": "بكالوريوس تجارة",
"graduation_year": "2004",
"university": "جامعة الزقازيق",
"grade": "مقبول",
"date_of_birth": "1983-03-15",
"national_id": "28303151203572",
"military_status": "إعفاء نهائى",
"gender": "male",
"phone": "1117008876",
"address": "4ش الظواهرى الحجاز مصر الجديدة القاهرة",
"salary": 7770.0,
"department": "الادارة المالية"
},
{
"name": "علا سيد مصطفى عبد العال",
"job_title": "محاسب ومراجع مالى",
"employee_code": "71",
"workforce_type": "insured",
"hire_date": "2024-07-01",
"insurance_date": "2024-10-01",
"insurance_number": "80185967",
"qualification": "بكالوريوس تجارة",
"graduation_year": "2021",
"university": "جامعة حلوان",
"grade": "جيد",
"date_of_birth": "1999-09-03",
"national_id": "29909030101623",
"military_status": "",
"gender": "female",
"phone": "1150823123",
"address": "816مدينة المستقبل الشروق القاهرة",
"salary": 7520.0,
"department": "الادارة المالية"
},
{
"name": "ايه احمد محمود",
"job_title": "مراجع مالى",
"employee_code": "214",
"workforce_type": "part_time",
"hire_date": "2026-07-05",
"insurance_date": "",
"insurance_number": "",
"qualification": "بكالوريوس تجارة",
"graduation_year": "2018",
"university": "المعهد العالى للدراسات التعاونية والادارية",
"grade": "مقبول",
"date_of_birth": "1997-01-19",
"national_id": "27901190102961",
"military_status": "",
"gender": "female",
"phone": "1001238190",
"address": "10السيد الببلاوى - الحلمية الجديدة - الخليفة - القاهرة",
"salary": 7000.0,
"department": "الادارة المالية"
},
{
"name": "علاءالدين محمد مجدى",
"job_title": "مسئول تحصيل",
"employee_code": "33",
"workforce_type": "part_time",
"hire_date": "2023-07-01",
"insurance_date": "",
"insurance_number": "55883458",
"qualification": "ليسانس حقوق",
"graduation_year": "2013",
"university": "جامعة بنى سويف",
"grade": "جيد",
"date_of_birth": "1992-06-27",
"national_id": "29206272200471",
"military_status": "أدى الخدمة",
"gender": "male",
"phone": "1208077718",
"address": "4ش محسن بمقبل بنى سويف - بنى سويف",
"salary": 7180.0,
"department": "الادارة المالية"
},
{
"name": "وليد حسين",
"job_title": "رئيس خزينة",
"employee_code": "",
"workforce_type": "seconded",
"hire_date": "وزارة",
"insurance_date": "",
"insurance_number": "",
"qualification": "إستعانه",
"graduation_year": "",
"university": "",
"grade": "",
"date_of_birth": "",
"national_id": "",
"military_status": "",
"gender": "male",
"phone": "",
"address": "",
"salary": 0,
"department": "الادارة المالية"
},
{
"name": "مصطفي سامي احمد",
"job_title": "امين خزنة فرعي",
"employee_code": "48",
"workforce_type": "insured",
"hire_date": "2023-01-01",
"insurance_date": "2024-09-01",
"insurance_number": "222151",
"qualification": "بكالوريوس تجارة خارجية",
"graduation_year": "2021",
"university": "معهد الألسن العالى",
"grade": "مقبول",
"date_of_birth": "1999-12-28",
"national_id": "29912280100658",
"military_status": "إعفاء مؤقت",
"gender": "male",
"phone": "1118108353",
"address": "5زقاق الساقية الجمالية القاهرة",
"salary": 7770.0,
"department": "الادارة المالية"
},
{
"name": "إسلام محمد حسن",
"job_title": "امين خزنة فرعي",
"employee_code": "47",
"workforce_type": "part_time",
"hire_date": "2024-10-01",
"insurance_date": "",
"insurance_number": "81519701",
"qualification": "بكالوريوس تجارة",
"graduation_year": "2020",
"university": "المعهد العالى للدراسات النوعية",
"grade": "جيد",
"date_of_birth": "1998-12-04",
"national_id": "29812042100572",
"military_status": "أدى الخدمة",
"gender": "male",
"phone": "1030622283",
"address": "4ح القرنفل العامل مستشفى الصدر العمرانية الجيزة",
"salary": 7770.0,
"department": "الادارة المالية"
},
{
"name": "صلاح هاني صلاح",
"job_title": "امين خزنة فرعي",
"employee_code": "208",
"workforce_type": "part_time",
"hire_date": "2026-06-01",
"insurance_date": "",
"insurance_number": "88528317",
"qualification": "بكالوريوس تجارة",
"graduation_year": "2024",
"university": "جامعة طنطا",
"grade": "جيد",
"date_of_birth": "2002-08-08",
"national_id": "30208081202596",
"military_status": "أدى الخدمة",
"gender": "male",
"phone": "1211013580",
"address": "ميت الفرماوى - مركز ميت غمر - الدقهلية",
"salary": 7000.0,
"department": "الادارة المالية"
},
{
"name": "طعمة نبيل حسنين",
"job_title": "امين خزنة فرعي",
"employee_code": "209",
"workforce_type": "part_time",
"hire_date": "2026-06-01",
"insurance_date": "",
"insurance_number": "70216004",
"qualification": "ليسانس حقوق",
"graduation_year": "2002",
"university": "جامعة الزقازيق",
"grade": "مقبول",
"date_of_birth": "1980-08-18",
"national_id": "28008180100489",
"military_status": "",
"gender": "female",
"phone": "1026541900",
"address": "26ش التحرير - مدينة البكرى - - مسطرد - شبرا الخيمة - القليوبية",
"salary": 7000.0,
"department": "الادارة المالية"
},
{
"name": "اروي تيسير قنديل",
"job_title": "امين خزنة فرعي",
"employee_code": "",
"workforce_type": "seconded",
"hire_date": "وزارة",
"insurance_date": "",
"insurance_number": "",
"qualification": "إستعانه",
"graduation_year": "",
"university": "",
"grade": "",
"date_of_birth": "",
"national_id": "",
"military_status": "",
"gender": "male",
"phone": "",
"address": "",
"salary": 0,
"department": "الادارة المالية"
},
{
"name": "محمد إسماعيل",
"job_title": "مدير مخازن",
"employee_code": "109",
"workforce_type": "seconded",
"hire_date": "وزارة",
"insurance_date": "",
"insurance_number": "",
"qualification": "دبلوم صناعى",
"graduation_year": "",
"university": "",
"grade": "",
"date_of_birth": "",
"national_id": "",
"military_status": "",
"gender": "male",
"phone": "",
"address": "",
"salary": 5000.0,
"department": "ادارة المخازن"
},
{
"name": "محمود مجدي احمد",
"job_title": "أمين عهدة",
"employee_code": "",
"workforce_type": "seconded",
"hire_date": "وزارة",
"insurance_date": "",
"insurance_number": "",
"qualification": "إستعانه",
"graduation_year": "",
"university": "",
"grade": "",
"date_of_birth": "",
"national_id": "",
"military_status": "",
"gender": "male",
"phone": "",
"address": "",
"salary": 0,
"department": "ادارة المخازن"
},
{
"name": "ريهام طلعت محمود",
"job_title": "اداري",
"employee_code": "5",
"workforce_type": "insured",
"hire_date": "2026-09-01",
"insurance_date": "2023-01-31",
"insurance_number": "81757832",
"qualification": "دبلوم معهد فنى تجارى",
"graduation_year": "2008",
"university": "المعهد الفنى التجارى بالمطرية",
"grade": "مقبول",
"date_of_birth": "1988-03-30",
"national_id": "28803300102043",
"military_status": "",
"gender": "female",
"phone": "1069079700",
"address": "3مساكن شرق حلوان حلوان القاهرة",
"salary": 7070.0,
"department": "ادارة المخازن"
},
{
"name": "محمد عبد العزيز يوسف",
"job_title": "مدير الإشراف والمتابعه",
"employee_code": "206",
"workforce_type": "part_time",
"hire_date": "2026-06-01",
"insurance_date": "",
"insurance_number": "",
"qualification": "ليسانس حقوق",
"graduation_year": "2001",
"university": "جامعة القاهرة",
"grade": "مقبول",
"date_of_birth": "1976-08-13",
"national_id": "27608132101795",
"military_status": "إعفاء نهائى",
"gender": "male",
"phone": "1000087894",
"address": "3ش الأوقاف - صلاح الدين - الكيت كات - إمبابة - جيزة",
"salary": 17000.0,
"department": "ادارة الاشراف والمتابعة"
},
{
"name": "أيمن محمد عبدربه",
"job_title": "نائب مدير الاشراف و المتابعة",
"employee_code": "103",
"workforce_type": "seconded",
"hire_date": "وزارة",
"insurance_date": "",
"insurance_number": "",
"qualification": "بكالوريوس إدارة أعمال",
"graduation_year": "",
"university": "",
"grade": "",
"date_of_birth": "",
"national_id": "",
"military_status": "",
"gender": "male",
"phone": "",
"address": "",
"salary": 8000.0,
"department": "ادارة الاشراف والمتابعة"
},
{
"name": "زياد فرج أحمد",
"job_title": "اشراف ادارى",
"employee_code": "27",
"workforce_type": "insured",
"hire_date": "2022-09-01",
"insurance_date": "2023-01-31",
"insurance_number": "73051601",
"qualification": "بكالوريوس سياحة وفنادق",
"graduation_year": "2021",
"university": "معهد الالسن العالي",
"grade": "مقبول",
"date_of_birth": "1999-09-15",
"national_id": "29909150108311",
"military_status": "إعفاء نهائى",
"gender": "male",
"phone": "1129959860",
"address": "19ش د زكي مبارك منية السيرج الساحل القاهرة",
"salary": 6780.0,
"department": "ادارة الاشراف والمتابعة"
},
{
"name": "شيماء حمدي طاهر",
"job_title": "اشراف اداري",
"employee_code": "25",
"workforce_type": "insured",
"hire_date": "2022-09-01",
"insurance_date": "2023-01-31",
"insurance_number": "82291099",
"qualification": "بكالوريوس خدمة اجتماعية",
"graduation_year": "2022",
"university": "المعهد العالي",
"grade": "جيد جدا",
"date_of_birth": "2000-04-06",
"national_id": "30004060103224",
"military_status": "",
"gender": "female",
"phone": "1063826129",
"address": "9ش الرشاد المطرية القاهرة",
"salary": 6900.0,
"department": "ادارة الاشراف والمتابعة"
},
{
"name": "إسلام سمير أحمد",
"job_title": "اشراف اداري",
"employee_code": "26",
"workforce_type": "insured",
"hire_date": "2023-04-01",
"insurance_date": "2023-04-01",
"insurance_number": "61953309",
"qualification": "بكالوريوس تجارة خارجية",
"graduation_year": "2016",
"university": "معهد الالسن العالي",
"grade": "مقبول",
"date_of_birth": "1993-01-17",
"national_id": "29301170106198",
"military_status": "إعفاء نهائى",
"gender": "male",
"phone": "1226012270",
"address": "مساكن الجمهورية ب10 م2 أول السلام القاهرة",
"salary": 6780.0,
"department": "ادارة الاشراف والمتابعة"
},
{
"name": "إسلام محمد مصطفى",
"job_title": "اشراف اداري",
"employee_code": "99",
"workforce_type": "insured",
"hire_date": "2026-01-01",
"insurance_date": "2026-02-01",
"insurance_number": "73351299",
"qualification": "دبلوم فنى صناعى",
"graduation_year": "",
"university": "",
"grade": "",
"date_of_birth": "1998-04-09",
"national_id": "29804090100271",
"military_status": "أدى الخدمة",
"gender": "male",
"phone": "1141708901",
"address": "26ش سبيل الخازندار - الوايلى - القاهرة",
"salary": 6100.0,
"department": "ادارة الاشراف والمتابعة"
},
{
"name": "محمد يحيي عتريس",
"job_title": "اشراف اداري",
"employee_code": "41",
"workforce_type": "insured",
"hire_date": "2022-09-01",
"insurance_date": "2023-03-31",
"insurance_number": "62421970",
"qualification": "بكالوريوس تجارة",
"graduation_year": "2008",
"university": "جامعة عين شمس",
"grade": "مقبول",
"date_of_birth": "1984-08-28",
"national_id": "28408280102193",
"military_status": "إعفاء نهائى",
"gender": "male",
"phone": "1004784782",
"address": "42مساكن شيراتون النزهة القاهرة",
"salary": 7390.0,
"department": "ادارة الاشراف والمتابعة"
},
{
"name": "د / عمرو محمد عبد العظيم",
"job_title": "مدير الإدارة الطبية",
"employee_code": "210",
"workforce_type": "part_time",
"hire_date": "2026-01-15",
"insurance_date": "",
"insurance_number": "",
"qualification": "ماجستير فى جراحة العظام",
"graduation_year": "2022",
"university": "جامعة بنها",
"grade": "جيد",
"date_of_birth": "1988-06-18",
"national_id": "28806181400714",
"military_status": "إعفاء نهائى",
"gender": "male",
"phone": "1120253653",
"address": "38ش راتب باشا - الساحل - القاهرة",
"salary": 15000.0,
"department": "الادارة الطبية"
},
{
"name": "د / محمد حازم نصر الدين سلامه",
"job_title": "طبيب بشرى",
"employee_code": "212",
"workforce_type": "part_time",
"hire_date": "2026-06-01",
"insurance_date": "",
"insurance_number": "74780235",
"qualification": "بكالوريوس فى الطب والجراحة",
"graduation_year": "2021",
"university": "جامعة المنوفية",
"grade": "جيد جدا",
"date_of_birth": "1997-10-01",
"national_id": "29710011709091",
"military_status": "إعفاء نهائى",
"gender": "male",
"phone": "1095099453",
"address": "10ش طلعت حرب - الشهداء - مركز الشهداء - المنوفية",
"salary": 12000.0,
"department": "الادارة الطبية"
},
{
"name": "إسماعيل محمد إبراهيم",
"job_title": "اخصائي إصابات و تأهيل",
"employee_code": "23",
"workforce_type": "part_time",
"hire_date": "2022-12-01",
"insurance_date": "",
"insurance_number": "32466392",
"qualification": "دبلوم تجارة",
"graduation_year": "",
"university": "",
"grade": "",
"date_of_birth": "1984-01-29",
"national_id": "28401290102031",
"military_status": "ادى الخدمة",
"gender": "male",
"phone": "1223309795",
"address": "3ح إسماعيل إبراهيم ش محمود منطاوى المطرية القاهرة",
"salary": 8360.0,
"department": "الادارة الطبية"
},
{
"name": "أمل إبراهيم حسين بسيونى",
"job_title": "تمريض",
"employee_code": "74",
"workforce_type": "insured",
"hire_date": "2024-07-01",
"insurance_date": "2024-09-01",
"insurance_number": "16736151",
"qualification": "دبلوم فنى تمريض",
"graduation_year": "1999",
"university": "مدرسة تلا القليوبية",
"grade": "",
"date_of_birth": "1982-03-12",
"national_id": "28203121701123",
"military_status": "",
"gender": "female",
"phone": "1067008991",
"address": "32ش محمد عبده ع البكرى شبرا الخيمة ثان القليوبية",
"salary": 8090.0,
"department": "الادارة الطبية"
},
{
"name": "حسام مصطفى السيد مصطفى",
"job_title": "تمريض",
"employee_code": "93",
"workforce_type": "part_time",
"hire_date": "2026-01-15",
"insurance_date": "",
"insurance_number": "64124294",
"qualification": "طالب إمتياز كلية التمريض",
"graduation_year": "",
"university": "جامعة بنها",
"grade": "",
"date_of_birth": "2002-08-22",
"national_id": "30208221400474",
"military_status": "",
"gender": "male",
"phone": "1018290925",
"address": "الكوم الأحمر مركز شبين القناطر - القليوبية",
"salary": 10000.0,
"department": "الادارة الطبية"
},
{
"name": "وليد محمد السيد عفيفى",
"job_title": "مسعف",
"employee_code": "215",
"workforce_type": "part_time",
"hire_date": "2026-07-05",
"insurance_date": "",
"insurance_number": "",
"qualification": "طالب إمتياز كلية التمريض",
"graduation_year": "",
"university": "جامعة بنها",
"grade": "",
"date_of_birth": "2002-06-21",
"national_id": "30206211401615",
"military_status": "",
"gender": "male",
"phone": "1021108988",
"address": "منشأة الكرام - مركز شبين القناطر - القليوبية",
"salary": 8000.0,
"department": "الادارة الطبية"
},
{
"name": "احمد ناصر محمود دويدار",
"job_title": "مسعف",
"employee_code": "216",
"workforce_type": "part_time",
"hire_date": "2026-07-01",
"insurance_date": "",
"insurance_number": "72554300",
"qualification": "طالب إمتياز كلية التمريض",
"graduation_year": "",
"university": "جامعة بنها",
"grade": "",
"date_of_birth": "2002-09-23",
"national_id": "30209231401831",
"military_status": "",
"gender": "male",
"phone": "1001088642",
"address": "ش أحمد أبو سالم - كفر شبين - مركز شبين القناطر - القليوبية",
"salary": 8000.0,
"department": "الادارة الطبية"
},
{
"name": "مراد طارق محمود صالح",
"job_title": "مدير التسويق والمبيعات",
"employee_code": "87",
"workforce_type": "part_time",
"hire_date": "2025-06-01",
"insurance_date": "",
"insurance_number": "",
"qualification": "بكالوريوس تجارة",
"graduation_year": "2003",
"university": "جامعه القاهرة",
"grade": "مقبول",
"date_of_birth": "1981-09-16",
"national_id": "28109160102371",
"military_status": "أدى الخدمة",
"gender": "male",
"phone": "1156665999",
"address": "تجمع 3 - ع64 - محلية 6 - القطامية - القاهرة",
"salary": 17000.0,
"department": "ادارة التسويق والمبيعات"
},
{
"name": "ميادة محمد خضير حسن",
"job_title": "مسئول تسويق",
"employee_code": "85",
"workforce_type": "insured",
"hire_date": "2025-06-01",
"insurance_date": "2025-09-01",
"insurance_number": "82150693",
"qualification": "بكالوريوس تجارة",
"graduation_year": "2023",
"university": "جامعة بنها",
"grade": "جيد",
"date_of_birth": "2000-05-25",
"national_id": "30005251401264",
"military_status": "",
"gender": "female",
"phone": "1012601876",
"address": "عرب الشعار - الجعافرة - مركز شبين القناطر - القليوبية",
"salary": 9000.0,
"department": "ادارة التسويق والمبيعات"
},
{
"name": "تهاني محسن محمد محجوب",
"job_title": "مسئول مبيعات",
"employee_code": "205",
"workforce_type": "part_time",
"hire_date": "2026-05-01",
"insurance_date": "",
"insurance_number": "90108495",
"qualification": "دبلوم المدارس الفندقية",
"graduation_year": "",
"university": "",
"grade": "",
"date_of_birth": "2004-07-17",
"national_id": "30407170103561",
"military_status": "",
"gender": "female",
"phone": "1044696748",
"address": "101ش السودان - المهندسين - الدقى - الجيزة",
"salary": 7000.0,
"department": "ادارة التسويق والمبيعات"
},
{
"name": "شهد سيد حسين",
"job_title": "مسئول مبيعات",
"employee_code": "213",
"workforce_type": "part_time",
"hire_date": "2026-07-01",
"insurance_date": "",
"insurance_number": "",
"qualification": "بكالوريوس فى الإعلام",
"graduation_year": "2024",
"university": "الجامعة العربية المفتوحة",
"grade": "جيد",
"date_of_birth": "2002-11-26",
"national_id": "30211260102864",
"military_status": "",
"gender": "female",
"phone": "1060177741",
"address": "37صقر قريش - شيراتون - النزهة - القاهرة",
"salary": 7000.0,
"department": "ادارة التسويق والمبيعات"
},
{
"name": "هبة محمود",
"job_title": "مدير الشئون القانونية",
"employee_code": "32",
"workforce_type": "insured",
"hire_date": "2022-09-01",
"insurance_date": "2023-01-31",
"insurance_number": "75334962",
"qualification": "ليسانس حقوق",
"graduation_year": "1999",
"university": "جامعة القاهرة",
"grade": "مقبول",
"date_of_birth": "1978-08-08",
"national_id": "27808080100166",
"military_status": "",
"gender": "female",
"phone": "1007442436",
"address": "105ش محمد فريد عابدين القاهرة",
"salary": 8600.0,
"department": "ادارة الشئون القانونية"
},
{
"name": "محمد سعد محمد",
"job_title": "مدير الموارد البشرية",
"employee_code": "61",
"workforce_type": "part_time",
"hire_date": "2024-03-01",
"insurance_date": "",
"insurance_number": "66000976",
"qualification": "ليسانس دار علوم",
"graduation_year": "2010",
"university": "جامعة القاهرة",
"grade": "مقبول",
"date_of_birth": "1988-06-27",
"national_id": "28806272102457",
"military_status": "اعفاء نهائي",
"gender": "male",
"phone": "1225370853",
"address": "18ش محمود فهمي حجازي الاريزونا فيصل",
"salary": 8950.0,
"department": "ادارة الموارد البشرية"
},
{
"name": "مرفت حنفى محمد المحلاوى",
"job_title": "نائب مدير الموارد البشرية",
"employee_code": "40",
"workforce_type": "part_time",
"hire_date": "2022-10-01",
"insurance_date": "",
"insurance_number": "5118375",
"qualification": "بكالوريوس علوم وتربية",
"graduation_year": "2003",
"university": "جامعة عين شمس",
"grade": "جيد",
"date_of_birth": "1982-06-21",
"national_id": "28206211601984",
"military_status": "",
"gender": "female",
"phone": "1225419807",
"address": "27ش محمد شفيق - أمام مستشفى الدعاة - النزهة - القاهرة",
"salary": 7700.0,
"department": "ادارة الموارد البشرية"
},
{
"name": "مصطفى سيد علي",
"job_title": "مدير اشتراكات عضوية",
"employee_code": "62",
"workforce_type": "part_time",
"hire_date": "2024-03-01",
"insurance_date": "",
"insurance_number": "40525654",
"qualification": "بكالوريوس تجارة تعليم مفتوح",
"graduation_year": "2017",
"university": "جامعة القاهرة",
"grade": "مقبول",
"date_of_birth": "1987-08-11",
"national_id": "28708110102117",
"military_status": "اعفاء مؤقت",
"gender": "male",
"phone": "1009535341",
"address": "ع76 - م5 - شركة الشمس - حدائق أكتوبر - 6أكتوبر - الجيزة",
"salary": 8950.0,
"department": "ادارة اشتراكات العضوية"
},
{
"name": "غادة جمال",
"job_title": "نائب مدير اشتراكات عضوية",
"employee_code": "118",
"workforce_type": "seconded",
"hire_date": "وزارة",
"insurance_date": "",
"insurance_number": "",
"qualification": "دبلوم تجارة",
"graduation_year": "",
"university": "",
"grade": "",
"date_of_birth": "",
"national_id": "",
"military_status": "",
"gender": "male",
"phone": "",
"address": "",
"salary": 4000.0,
"department": "ادارة اشتراكات العضوية"
},
{
"name": "هالة كارم محمد",
"job_title": "مسئول اشتراكات عضوية",
"employee_code": "42",
"workforce_type": "part_time",
"hire_date": "2023-04-01",
"insurance_date": "",
"insurance_number": "35756340",
"qualification": "ليسانس أداب تعليم مفتوح",
"graduation_year": "2019",
"university": "جامعة المنوفية",
"grade": "جيد",
"date_of_birth": "1981-09-08",
"national_id": "28109081700122",
"military_status": "",
"gender": "female",
"phone": "1015649149",
"address": "كفر المصيلحة شبين الكوم المنوفية",
"salary": 7180.0,
"department": "ادارة اشتراكات العضوية"
},
{
"name": "محمد عبد المنعم محمود",
"job_title": "مسئول اشتراكات عضوية",
"employee_code": "46",
"workforce_type": "insured",
"hire_date": "2022-10-01",
"insurance_date": "2023-01-31",
"insurance_number": "82968088",
"qualification": "بكالوريوس خدمة اجتماعية",
"graduation_year": "2009",
"university": "المعهد العالي ببنها",
"grade": "جيد",
"date_of_birth": "1988-03-20",
"national_id": "28803201306656",
"military_status": "اعفاء مؤقت",
"gender": "male",
"phone": "1004784782",
"address": "الشرقاية مركز كفر صقر الشرقية",
"salary": 7180.0,
"department": "ادارة اشتراكات العضوية"
},
{
"name": "احمد انور محمد",
"job_title": "مدير النظم والمعلومات",
"employee_code": "63",
"workforce_type": "part_time",
"hire_date": "2024-03-01",
"insurance_date": "",
"insurance_number": "",
"qualification": "ليسانس حقوق",
"graduation_year": "2011",
"university": "كلية الحقوق",
"grade": "مقبول",
"date_of_birth": "1990-03-01",
"national_id": "29003010201673",
"military_status": "ادى الخدمة",
"gender": "male",
"phone": "1099994341",
"address": "ش الفردوس من مسجد الشرش الهانوفيل الدخيلة الاسكندرية",
"salary": 18370.0,
"department": "ادارة النظم والمعلومات"
},
{
"name": "يوسف محمد مصطفى يوسف",
"job_title": "مراقب كاميرات",
"employee_code": "67",
"workforce_type": "part_time",
"hire_date": "2024-06-01",
"insurance_date": "",
"insurance_number": "0855168-5",
"qualification": "دبلوم تجارة",
"graduation_year": "2019",
"university": "خدمات الظاهر الفنية التجارية",
"grade": "",
"date_of_birth": "1999-11-09",
"national_id": "29911090103514",
"military_status": "",
"gender": "male",
"phone": "1008607602",
"address": "629ش بورسعيد الظاهر القاهرة",
"salary": 6670.0,
"department": "ادارة النظم والمعلومات"
},
{
"name": "محمود احمد عبد الفتاح عجلان",
"job_title": "مصمم ومبرمج ومطور",
"employee_code": "",
"workforce_type": "insured",
"hire_date": "2025-11-01",
"insurance_date": "2026-04-01",
"insurance_number": "88498960",
"qualification": "بكالوريوس فى علوم الحاسب",
"graduation_year": "2024",
"university": "مودرن أكاديمى بالمعادى",
"grade": "مقبول",
"date_of_birth": "2001-05-29",
"national_id": "30105290104638",
"military_status": "إعفاء نهائى",
"gender": "male",
"phone": "1060929653",
"address": "4042زهراء مدينة نصر - مرحلة سابعة - مدينة نصر أول القاهرة",
"salary": 15000.0,
"department": "ادارة النظم والمعلومات"
},
{
"name": "شيرين محمد حسين",
"job_title": "مصمم ومبرمج ومطور",
"employee_code": "",
"workforce_type": "part_time",
"hire_date": "2026-04-01",
"insurance_date": "",
"insurance_number": "80423011",
"qualification": "بكالوريوس فنون جميلة",
"graduation_year": "2024",
"university": "جامعة حلوان",
"grade": "جيد جدا",
"date_of_birth": "2001-12-01",
"national_id": "30112012102728",
"military_status": "",
"gender": "female",
"phone": "1554143365",
"address": "17ش عمان الدقى - الجيزة",
"salary": 17000.0,
"department": "ادارة النظم والمعلومات"
},
{
"name": "أحمد عبد الله أحمد بكير",
"job_title": "إمام المسجد",
"employee_code": "73",
"workforce_type": "part_time",
"hire_date": "2024-07-01",
"insurance_date": "",
"insurance_number": "",
"qualification": "دكتوراة فى الشريعة الإسلامية",
"graduation_year": "2023",
"university": "جامعة القاهرة",
"grade": "الشرف الأولى",
"date_of_birth": "1982-04-05",
"national_id": "28204051701518",
"military_status": "ادى الخدمة",
"gender": "male",
"phone": "1066072718",
"address": "ش أبو بكر الصديق من ش الزهور مدينة نصر أول القاهرة",
"salary": 4670.0,
"department": "المسجد"
}
]
\ No newline at end of file
...@@ -208,6 +208,16 @@ The `members` table is the central hub. Nearly every module in the ERP reference ...@@ -208,6 +208,16 @@ The `members` table is the central hub. Nearly every module in the ERP reference
- BillingService (discount line item) - BillingService (discount line item)
- Pricing/SpecialDiscountService (bonus free subscription years) - Pricing/SpecialDiscountService (bonus free subscription years)
### `regulatory_discounts` (bylaw discounts — Arts 97-102, 110)
- Pricing/RegulatoryDiscountService (eligibility evaluation)
- Pricing/PricingEngine (applyRegulatoryDiscount integration)
- Members (cross-branch verification via members table)
- HR (club employee verification via hr_employee_profiles)
### `regulatory_discount_applications` (audit trail)
- Pricing/RegulatoryDiscountController (approval workflow)
- Members (member_id FK for applied discounts)
--- ---
## Workflow Dependencies ## Workflow Dependencies
...@@ -307,3 +317,86 @@ This is application-level validation, NOT database-level cross-table constraints ...@@ -307,3 +317,86 @@ This is application-level validation, NOT database-level cross-table constraints
- `payment.*` → Payments module - `payment.*` → Payments module
Super admin check: `employee_roles JOIN roles WHERE role_code = 'super_admin'` Super admin check: `employee_roles JOIN roles WHERE role_code = 'super_admin'`
---
## HR Module Dependencies
### HR Module
| Depends On | How |
|------------|-----|
| `employees` table (Auth) | `employee_id` FK — links HR profile to system login |
| Workflow | Approval chains: `hr_leave_approval`, `hr_loan_approval`, `hr_contract_approval` |
| Members (NationalIdParser) | Parses 14-digit national ID for DOB + gender during employee creation |
| `system_config` | 40+ HR config keys for all calculations |
### Modules That Depend On HR
| Module | How |
|--------|-----|
| Accounting (bootstrap) | Listens to `hr.payroll.paid` → auto-posts salary journal entry |
| Accounting (GLSyncService) | Reads `hr_payroll_runs` WHERE status='paid' for batch GL sync |
| Pricing (RegulatoryDiscounts) | Reads `hr_employee_profiles` to verify club employee status for Art.102 discount |
### HR Events → Listeners
| Event | Dispatched By | Listened By |
|-------|--------------|-------------|
| `hr.leave.submitted` | LeaveService | — |
| `hr.leave.approved` | LeaveService | HR bootstrap → SMS dispatch |
| `hr.leave.rejected` | LeaveService | HR bootstrap → SMS dispatch |
| `hr.payroll.paid` | PayrollController | HR bootstrap → SMS; Accounting → journal |
| `hr.loan.approved` | LoanService | HR bootstrap → SMS dispatch |
| `hr.contract.renewed` | ContractService | — |
| `hr.contract.terminated` | ContractService | — |
| `hr.attendance.recorded` | AttendanceController | HR bootstrap → AttendanceViolationService |
| `attendance.violations_detected` | AttendanceViolationService | — |
### HR Database Dependencies (Shared Read)
| Table | Read By |
|-------|---------|
| `employees` | HR (employee_id link) |
| `system_config` (hr.* keys) | All HR services (insurance, tax, leave, overtime, discipline, EOS) |
| `hr_employee_profiles` | Pricing/RegulatoryDiscounts (employee verification) |
| `hr_payroll_runs` | Accounting/GLSyncService (salary journal sync) |
### If `hr_employee_profiles.basic_salary` changes:
- Payroll: next period calculates new gross/net
- Insurance: new contribution amounts
- Tax: new bracket placement
- Loans: installment vs salary ratio affected
- End of Service: settlement based on last salary
### If `hr_employee_profiles.employment_status` changes to terminated/resigned:
- Active contract should be terminated
- Leave balances frozen
- Outstanding loans may be deducted from EOS settlement
- Biometric punches ignored
- Payroll excludes from next period
### If insurance config (rates/caps) changes:
- Applies to next payroll calculation
- Previous periods' records are immutable
- Form 1/2/6 reports reflect current config
### HR Permission Keys
- `hr.department.*` — Department management
- `hr.job_title.*` — Job titles
- `hr.employee.*` — Employee profiles & salary
- `hr.contract.*` — Contracts
- `hr.attendance.*` — Attendance
- `hr.leave.*` — Leaves
- `hr.payroll.*` — Payroll
- `hr.insurance.*` — Insurance
- `hr.tax.*` — Tax
- `hr.disciplinary.*` — Disciplinary
- `hr.loan.*` — Loans
- `hr.eos.*` — End of Service
- `hr.performance.*` — Performance
- `hr.document.*` — Documents
- `hr.holiday.*` — Holidays
- `hr.schedule.*` — Work schedules
- `hr.overtime.*` — Overtime
- `hr.shifts.*` — Shifts
- `hr.permissions.*` — Permission requests (hourly leaves)
- `hr.biometric.*` — Biometric devices
- `hr.report.*` — Reports
- `hr.payslip.view_own` — Self-service payslip
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