Commit cc10c16d authored by Mahmoud Aglan's avatar Mahmoud Aglan

xfgdf

parent da42f02a
......@@ -16,7 +16,9 @@
"Bash(ls /Users/mahmoudaglan/clubphp/database/seeds/Phase_22*)",
"Bash(ls database/migrations/Phase_1[7-9]_* database/migrations/Phase_2[0-3]_*)",
"Bash(ls database/seeds/Phase_1[7-9]_* database/seeds/Phase_2[0-2]_*)",
"Bash(ls cron/jobs/*Job.php)"
"Bash(ls cron/jobs/*Job.php)",
"Bash(ls /Users/mahmoudaglan/clubphp/database/migrations/Phase_24_*)",
"Bash(ls -la /Users/mahmoudaglan/clubphp/database/migrations/Phase_24_*)"
]
}
}
......@@ -27,6 +27,9 @@ return [
`notes` TEXT NULL,
`created_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
`updated_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
`is_archived` TINYINT(1) NOT NULL DEFAULT 0,
`archived_at` TIMESTAMP NULL DEFAULT NULL,
`archived_by` BIGINT UNSIGNED NULL,
`created_by` BIGINT UNSIGNED NULL,
UNIQUE KEY `uq_reservations_number` (`reservation_number`),
INDEX `idx_reservations_facility` (`facility_id`),
......
......@@ -25,6 +25,9 @@ return [
`notes` TEXT NULL,
`created_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
`updated_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
`is_archived` TINYINT(1) NOT NULL DEFAULT 0,
`archived_at` TIMESTAMP NULL DEFAULT NULL,
`archived_by` BIGINT UNSIGNED NULL,
`created_by` BIGINT UNSIGNED NULL,
INDEX `idx_as_player` (`player_id`),
INDEX `idx_as_enrollment` (`enrollment_id`),
......
<?php
declare(strict_types=1);
return [
'up' => "ALTER TABLE `reservations`
ADD COLUMN `is_archived` TINYINT(1) NOT NULL DEFAULT 0 AFTER `created_by`,
ADD COLUMN `archived_at` TIMESTAMP NULL DEFAULT NULL AFTER `is_archived`,
ADD COLUMN `archived_by` BIGINT UNSIGNED NULL AFTER `archived_at`;
ALTER TABLE `activity_subscriptions`
ADD COLUMN `is_archived` TINYINT(1) NOT NULL DEFAULT 0 AFTER `created_by`,
ADD COLUMN `archived_at` TIMESTAMP NULL DEFAULT NULL AFTER `is_archived`,
ADD COLUMN `archived_by` BIGINT UNSIGNED NULL AFTER `archived_at`",
'down' => "ALTER TABLE `reservations`
DROP COLUMN `archived_by`,
DROP COLUMN `archived_at`,
DROP COLUMN `is_archived`;
ALTER TABLE `activity_subscriptions`
DROP COLUMN `archived_by`,
DROP COLUMN `archived_at`,
DROP COLUMN `is_archived`",
];
......@@ -12,14 +12,14 @@ return function (Database $db): void {
if (!$row) {
throw new \RuntimeException("Sport discipline '{$code}' not found. Run Phase 17 seeds first.");
}
return (int) $row->id;
return (int) $row['id'];
};
// ── Helper: insert academy idempotently, return its ID ──
$ensureAcademy = function (array $data) use ($db, $ts): int {
$existing = $db->selectOne("SELECT id FROM academies WHERE code = ?", [$data['code']]);
if ($existing) {
return (int) $existing->id;
return (int) $existing['id'];
}
$db->insert('academies', array_merge($data, [
'is_active' => 1,
......@@ -27,7 +27,7 @@ return function (Database $db): void {
'updated_at' => $ts,
]));
$inserted = $db->selectOne("SELECT id FROM academies WHERE code = ?", [$data['code']]);
return (int) $inserted->id;
return (int) $inserted['id'];
};
// ── Helper: insert level idempotently ──
......
......@@ -8,13 +8,11 @@ return function (Database $db): void {
$schemas = [
[
'schema_code' => 'SPORTS_REGISTRATION_MEMBER',
'name_ar' => 'تسجيل لاعب عضو في الأنشطة الرياضية',
'name_en' => 'Sports Registration - Member',
'description_ar' => 'استمارة تسجيل لاعب عضو بالنادي في الأنشطة الرياضية والأكاديميات',
'fee_amount' => '50.00',
'related_service' => 'SVC_SPORTS_REG_MEMBER',
'fields_json' => json_encode([
'form_code' => 'SPORTS_REGISTRATION_MEMBER',
'name_ar' => 'تسجيل لاعب عضو في الأنشطة الرياضية',
'name_en' => 'Sports Registration - Member',
'form_fee' => '50.00',
'schema_json' => json_encode([
['key' => 'member_id', 'type' => 'hidden', 'label_ar' => 'رقم العضو', 'required' => true, 'auto_populated' => true, 'order' => 1],
['key' => 'discipline_ids', 'type' => 'multi_select', 'label_ar' => 'الأنشطة الرياضية المطلوبة', 'required' => true, 'data_source' => 'sport_disciplines', 'order' => 2],
['key' => 'academy_id', 'type' => 'select', 'label_ar' => 'الأكاديمية', 'required' => false, 'data_source' => 'academies', 'order' => 3],
......@@ -27,22 +25,20 @@ return function (Database $db): void {
], JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT),
],
[
'schema_code' => 'SPORTS_REGISTRATION_NONMEMBER',
'name_ar' => 'تسجيل لاعب غير عضو في الأنشطة الرياضية',
'name_en' => 'Sports Registration - Non-Member',
'description_ar' => 'استمارة تسجيل لاعب من خارج النادي في الأنشطة الرياضية والأكاديميات',
'fee_amount' => '100.00',
'related_service' => 'SVC_SPORTS_REG_NONMEMBER',
'fields_json' => json_encode([
'form_code' => 'SPORTS_REGISTRATION_NONMEMBER',
'name_ar' => 'تسجيل لاعب غير عضو في الأنشطة الرياضية',
'name_en' => 'Sports Registration - Non-Member',
'form_fee' => '100.00',
'schema_json' => json_encode([
['key' => 'full_name_ar', 'type' => 'text', 'label_ar' => 'الاسم الكامل بالعربي', 'required' => true, 'validation' => 'required|string|min:10|max:200', 'order' => 1, 'width' => 'full'],
['key' => 'full_name_en', 'type' => 'text', 'label_ar' => 'الاسم بالإنجليزي', 'required' => false, 'validation' => 'nullable|string|max:200', 'order' => 2, 'width' => 'half'],
['key' => 'national_id', 'type' => 'text', 'label_ar' => 'الرقم القومي', 'required' => true, 'validation' => 'required|digits:14', 'order' => 3, 'width' => 'half'],
['key' => 'date_of_birth', 'type' => 'date', 'label_ar' => 'تاريخ الميلاد', 'required' => true, 'validation' => 'required|date', 'order' => 4, 'width' => 'half'],
['key' => 'gender', 'type' => 'select', 'label_ar' => 'النوع', 'required' => true, 'validation' => 'required|in:male,female', 'options' => [['value' => 'male', 'label_ar' => 'ذكر'], ['value' => 'female', 'label_ar' => 'أنثى']], 'order' => 5, 'width' => 'half'],
['key' => 'gender', 'type' => 'select', 'label_ar' => 'النوع', 'required' => true, 'options' => [['value' => 'male', 'label_ar' => 'ذكر'], ['value' => 'female', 'label_ar' => 'أنثى']], 'order' => 5, 'width' => 'half'],
['key' => 'phone', 'type' => 'text', 'label_ar' => 'الهاتف', 'required' => true, 'validation' => 'required|string|max:20', 'order' => 6, 'width' => 'half'],
['key' => 'guardian_name', 'type' => 'text', 'label_ar' => 'اسم ولي الأمر', 'required' => false, 'conditional' => true, 'show_if' => ['field' => 'age', 'operator' => 'lt', 'value' => 18], 'validation' => 'required_if:age_lt_18|string|max:200', 'order' => 7, 'width' => 'half'],
['key' => 'guardian_phone', 'type' => 'text', 'label_ar' => 'هاتف ولي الأمر', 'required' => false, 'conditional' => true, 'show_if' => ['field' => 'age', 'operator' => 'lt', 'value' => 18], 'validation' => 'required_if:age_lt_18|string|max:20', 'order' => 8, 'width' => 'half'],
['key' => 'guardian_national_id', 'type' => 'text', 'label_ar' => 'الرقم القومي لولي الأمر', 'required' => false, 'conditional' => true, 'show_if' => ['field' => 'age', 'operator' => 'lt', 'value' => 18], 'validation' => 'nullable|digits:14', 'order' => 9, 'width' => 'half'],
['key' => 'guardian_name', 'type' => 'text', 'label_ar' => 'اسم ولي الأمر', 'required' => false, 'conditional' => true, 'show_if' => ['field' => 'age', 'operator' => 'lt', 'value' => 18], 'order' => 7, 'width' => 'half'],
['key' => 'guardian_phone', 'type' => 'text', 'label_ar' => 'هاتف ولي الأمر', 'required' => false, 'conditional' => true, 'show_if' => ['field' => 'age', 'operator' => 'lt', 'value' => 18], 'order' => 8, 'width' => 'half'],
['key' => 'guardian_national_id', 'type' => 'text', 'label_ar' => 'الرقم القومي لولي الأمر', 'required' => false, 'conditional' => true, 'show_if' => ['field' => 'age', 'operator' => 'lt', 'value' => 18], 'order' => 9, 'width' => 'half'],
['key' => 'guardian_relationship', 'type' => 'select', 'label_ar' => 'صلة القرابة', 'required' => false, 'conditional' => true, 'show_if' => ['field' => 'age', 'operator' => 'lt', 'value' => 18], 'options' => [['value' => 'father', 'label_ar' => 'الأب'], ['value' => 'mother', 'label_ar' => 'الأم'], ['value' => 'brother', 'label_ar' => 'الأخ'], ['value' => 'sister', 'label_ar' => 'الأخت'], ['value' => 'other', 'label_ar' => 'أخرى']], 'order' => 10, 'width' => 'half'],
['key' => 'discipline_ids', 'type' => 'multi_select', 'label_ar' => 'الأنشطة الرياضية المطلوبة', 'required' => true, 'data_source' => 'sport_disciplines', 'order' => 11],
['key' => 'academy_id', 'type' => 'select', 'label_ar' => 'الأكاديمية', 'required' => false, 'data_source' => 'academies', 'order' => 12],
......@@ -54,24 +50,20 @@ return function (Database $db): void {
];
foreach ($schemas as $s) {
$existing = $db->selectOne("SELECT id FROM form_schemas WHERE schema_code = ?", [$s['schema_code']]);
$existing = $db->selectOne("SELECT id FROM form_schemas WHERE form_code = ?", [$s['form_code']]);
if ($existing) {
continue;
}
$db->insert('form_schemas', [
'schema_code' => $s['schema_code'],
'name_ar' => $s['name_ar'],
'name_en' => $s['name_en'],
'description_ar' => $s['description_ar'],
'fields_json' => $s['fields_json'],
'validation_json' => null,
'fee_amount' => $s['fee_amount'],
'related_service' => $s['related_service'],
'config_json' => null,
'is_active' => 1,
'version' => 1,
'created_at' => $ts,
'updated_at' => $ts,
'form_code' => $s['form_code'],
'name_ar' => $s['name_ar'],
'name_en' => $s['name_en'],
'form_fee' => $s['form_fee'],
'schema_json' => $s['schema_json'],
'is_active' => 1,
'version' => 1,
'created_at' => $ts,
'updated_at' => $ts,
]);
}
};
......@@ -8,350 +8,92 @@ return function (Database $db): void {
$reports = [
[
'report_code' => 'RPT_PLAYER_TOTALS',
'name_ar' => 'إجمالي اللاعبين',
'name_en' => 'Player Totals',
'description_ar' => 'إجمالي اللاعبين حسب النوع (عضو/غير عضو)، الكروت النشطة/الموقوفة، وتوزيع النوع',
'category' => 'sports',
'query_config_json' => json_encode([
'table' => 'players',
'joins' => [
['table' => 'player_cards', 'on' => 'players.id = player_cards.player_id', 'type' => 'LEFT'],
],
'aggregations' => [
['function' => 'COUNT', 'field' => 'players.id', 'alias' => 'total_players'],
['function' => 'SUM', 'field' => 'CASE WHEN players.player_type = \'member\' THEN 1 ELSE 0 END', 'alias' => 'member_count'],
['function' => 'SUM', 'field' => 'CASE WHEN players.player_type = \'non_member\' THEN 1 ELSE 0 END', 'alias' => 'non_member_count'],
['function' => 'SUM', 'field' => 'CASE WHEN player_cards.status = \'active\' THEN 1 ELSE 0 END', 'alias' => 'active_cards'],
['function' => 'SUM', 'field' => 'CASE WHEN player_cards.status = \'inactive\' THEN 1 ELSE 0 END', 'alias' => 'inactive_cards'],
['function' => 'SUM', 'field' => 'CASE WHEN players.gender = \'male\' THEN 1 ELSE 0 END', 'alias' => 'male_count'],
['function' => 'SUM', 'field' => 'CASE WHEN players.gender = \'female\' THEN 1 ELSE 0 END', 'alias' => 'female_count'],
],
'group_by' => [],
], JSON_UNESCAPED_UNICODE),
'parameters_json' => json_encode([
['name' => 'date_from', 'type' => 'date', 'label_ar' => 'من تاريخ', 'required' => false],
['name' => 'date_to', 'type' => 'date', 'label_ar' => 'إلى تاريخ', 'required' => false],
], JSON_UNESCAPED_UNICODE),
'display_config_json' => json_encode([
'type' => 'summary_cards',
'columns' => ['total_players', 'member_count', 'non_member_count', 'active_cards', 'inactive_cards', 'male_count', 'female_count'],
], JSON_UNESCAPED_UNICODE),
'sort_order' => 1,
'report_code' => 'RPT_PLAYER_TOTALS',
'name_ar' => 'إجمالي اللاعبين',
'name_en' => 'Player Totals',
'description_ar' => 'إجمالي اللاعبين حسب النوع (عضو/غير عضو)، الكروت النشطة/الموقوفة، وتوزيع النوع',
'category' => 'sports',
'required_permission' => 'report.view_sports',
],
[
'report_code' => 'RPT_PLAYERS_BY_SPORT',
'name_ar' => 'اللاعبين حسب النشاط',
'name_en' => 'Players by Sport',
'description_ar' => 'عدد اللاعبين لكل نشاط رياضي',
'category' => 'sports',
'query_config_json' => json_encode([
'table' => 'player_disciplines',
'joins' => [
['table' => 'sport_disciplines', 'on' => 'player_disciplines.discipline_id = sport_disciplines.id', 'type' => 'INNER'],
['table' => 'players', 'on' => 'player_disciplines.player_id = players.id', 'type' => 'INNER'],
],
'aggregations' => [
['function' => 'COUNT', 'field' => 'players.id', 'alias' => 'player_count'],
],
'group_by' => ['sport_disciplines.id', 'sport_disciplines.name_ar'],
], JSON_UNESCAPED_UNICODE),
'parameters_json' => json_encode([
['name' => 'date_from', 'type' => 'date', 'label_ar' => 'من تاريخ', 'required' => false],
['name' => 'date_to', 'type' => 'date', 'label_ar' => 'إلى تاريخ', 'required' => false],
['name' => 'discipline_id', 'type' => 'select', 'label_ar' => 'النشاط الرياضي', 'required' => false, 'data_source' => 'sport_disciplines'],
], JSON_UNESCAPED_UNICODE),
'display_config_json' => json_encode([
'type' => 'table',
'columns' => ['sport_disciplines.name_ar', 'player_count'],
'chart' => ['type' => 'bar', 'x' => 'sport_disciplines.name_ar', 'y' => 'player_count'],
], JSON_UNESCAPED_UNICODE),
'sort_order' => 2,
'report_code' => 'RPT_PLAYERS_BY_SPORT',
'name_ar' => 'اللاعبين حسب النشاط',
'name_en' => 'Players by Sport',
'description_ar' => 'عدد اللاعبين لكل نشاط رياضي',
'category' => 'sports',
'required_permission' => 'report.view_sports',
],
[
'report_code' => 'RPT_PLAYERS_BY_ACADEMY',
'name_ar' => 'اللاعبين حسب الأكاديمية',
'name_en' => 'Players by Academy',
'description_ar' => 'عدد اللاعبين لكل أكاديمية ومستوى',
'category' => 'sports',
'query_config_json' => json_encode([
'table' => 'enrollments',
'joins' => [
['table' => 'academies', 'on' => 'enrollments.academy_id = academies.id', 'type' => 'INNER'],
['table' => 'academy_levels', 'on' => 'enrollments.level_id = academy_levels.id', 'type' => 'LEFT'],
['table' => 'players', 'on' => 'enrollments.player_id = players.id', 'type' => 'INNER'],
],
'aggregations' => [
['function' => 'COUNT', 'field' => 'players.id', 'alias' => 'player_count'],
],
'group_by' => ['academies.id', 'academies.name_ar', 'academy_levels.id', 'academy_levels.name_ar'],
], JSON_UNESCAPED_UNICODE),
'parameters_json' => json_encode([
['name' => 'academy_id', 'type' => 'select', 'label_ar' => 'الأكاديمية', 'required' => false, 'data_source' => 'academies'],
['name' => 'date_from', 'type' => 'date', 'label_ar' => 'من تاريخ', 'required' => false],
['name' => 'date_to', 'type' => 'date', 'label_ar' => 'إلى تاريخ', 'required' => false],
], JSON_UNESCAPED_UNICODE),
'display_config_json' => json_encode([
'type' => 'table',
'columns' => ['academies.name_ar', 'academy_levels.name_ar', 'player_count'],
], JSON_UNESCAPED_UNICODE),
'sort_order' => 3,
'report_code' => 'RPT_PLAYERS_BY_ACADEMY',
'name_ar' => 'اللاعبين حسب الأكاديمية',
'name_en' => 'Players by Academy',
'description_ar' => 'عدد اللاعبين لكل أكاديمية ومستوى',
'category' => 'sports',
'required_permission' => 'report.view_sports',
],
[
'report_code' => 'RPT_ENROLLMENT_STATUS',
'name_ar' => 'حالة التسجيلات',
'name_en' => 'Enrollment Status',
'description_ar' => 'حالة التسجيلات (نشط، موقوف، منسحب) لكل أكاديمية',
'category' => 'sports',
'query_config_json' => json_encode([
'table' => 'enrollments',
'joins' => [
['table' => 'academies', 'on' => 'enrollments.academy_id = academies.id', 'type' => 'INNER'],
],
'aggregations' => [
['function' => 'COUNT', 'field' => 'enrollments.id', 'alias' => 'total_enrollments'],
['function' => 'SUM', 'field' => 'CASE WHEN enrollments.status = \'active\' THEN 1 ELSE 0 END', 'alias' => 'active_count'],
['function' => 'SUM', 'field' => 'CASE WHEN enrollments.status = \'suspended\' THEN 1 ELSE 0 END', 'alias' => 'suspended_count'],
['function' => 'SUM', 'field' => 'CASE WHEN enrollments.status = \'dropped\' THEN 1 ELSE 0 END', 'alias' => 'dropped_count'],
],
'group_by' => ['academies.id', 'academies.name_ar'],
], JSON_UNESCAPED_UNICODE),
'parameters_json' => json_encode([
['name' => 'academy_id', 'type' => 'select', 'label_ar' => 'الأكاديمية', 'required' => false, 'data_source' => 'academies'],
['name' => 'date_from', 'type' => 'date', 'label_ar' => 'من تاريخ', 'required' => false],
['name' => 'date_to', 'type' => 'date', 'label_ar' => 'إلى تاريخ', 'required' => false],
], JSON_UNESCAPED_UNICODE),
'display_config_json' => json_encode([
'type' => 'table',
'columns' => ['academies.name_ar', 'total_enrollments', 'active_count', 'suspended_count', 'dropped_count'],
'chart' => ['type' => 'stacked_bar', 'x' => 'academies.name_ar', 'y' => ['active_count', 'suspended_count', 'dropped_count']],
], JSON_UNESCAPED_UNICODE),
'sort_order' => 4,
'report_code' => 'RPT_ENROLLMENT_STATUS',
'name_ar' => 'حالة التسجيلات',
'name_en' => 'Enrollment Status',
'description_ar' => 'حالة التسجيلات (نشط، موقوف، منسحب) لكل أكاديمية',
'category' => 'sports',
'required_permission' => 'report.view_sports',
],
[
'report_code' => 'RPT_FACILITY_UTILIZATION',
'name_ar' => 'إشغال الملاعب',
'name_en' => 'Facility Utilization',
'description_ar' => 'نسبة إشغال الملاعب حسب النوع، تغطية الفترات الزمنية، وعدد الحجوزات',
'category' => 'sports',
'query_config_json' => json_encode([
'table' => 'facilities',
'joins' => [
['table' => 'reservations', 'on' => 'facilities.id = reservations.facility_id', 'type' => 'LEFT'],
],
'aggregations' => [
['function' => 'COUNT', 'field' => 'reservations.id', 'alias' => 'booking_count'],
['function' => 'SUM', 'field' => 'reservations.duration_hours', 'alias' => 'total_hours_booked'],
['function' => 'AVG', 'field' => 'reservations.duration_hours', 'alias' => 'avg_hours_per_booking'],
],
'group_by' => ['facilities.id', 'facilities.name_ar', 'facilities.facility_type'],
], JSON_UNESCAPED_UNICODE),
'parameters_json' => json_encode([
['name' => 'date_from', 'type' => 'date', 'label_ar' => 'من تاريخ', 'required' => false],
['name' => 'date_to', 'type' => 'date', 'label_ar' => 'إلى تاريخ', 'required' => false],
['name' => 'facility_type', 'type' => 'select', 'label_ar' => 'نوع الملعب', 'required' => false],
], JSON_UNESCAPED_UNICODE),
'display_config_json' => json_encode([
'type' => 'table',
'columns' => ['facilities.name_ar', 'facilities.facility_type', 'booking_count', 'total_hours_booked', 'avg_hours_per_booking'],
'chart' => ['type' => 'bar', 'x' => 'facilities.name_ar', 'y' => 'booking_count'],
], JSON_UNESCAPED_UNICODE),
'sort_order' => 5,
'report_code' => 'RPT_FACILITY_UTILIZATION',
'name_ar' => 'إشغال الملاعب',
'name_en' => 'Facility Utilization',
'description_ar' => 'نسبة إشغال الملاعب حسب النوع، تغطية الفترات الزمنية، وعدد الحجوزات',
'category' => 'sports',
'required_permission' => 'report.view_sports',
],
[
'report_code' => 'RPT_RESERVATIONS',
'name_ar' => 'الحجوزات',
'name_en' => 'Reservations',
'description_ar' => 'عدد الحجوزات والإيرادات حسب الملعب والفترة الزمنية',
'category' => 'sports',
'query_config_json' => json_encode([
'table' => 'reservations',
'joins' => [
['table' => 'facilities', 'on' => 'reservations.facility_id = facilities.id', 'type' => 'INNER'],
],
'aggregations' => [
['function' => 'COUNT', 'field' => 'reservations.id', 'alias' => 'reservation_count'],
['function' => 'SUM', 'field' => 'reservations.total_amount', 'alias' => 'total_revenue'],
],
'group_by' => ['facilities.id', 'facilities.name_ar'],
], JSON_UNESCAPED_UNICODE),
'parameters_json' => json_encode([
['name' => 'date_from', 'type' => 'date', 'label_ar' => 'من تاريخ', 'required' => false],
['name' => 'date_to', 'type' => 'date', 'label_ar' => 'إلى تاريخ', 'required' => false],
['name' => 'facility_id', 'type' => 'select', 'label_ar' => 'الملعب', 'required' => false, 'data_source' => 'facilities'],
['name' => 'status', 'type' => 'select', 'label_ar' => 'حالة الحجز', 'required' => false, 'options' => ['confirmed', 'pending', 'cancelled']],
], JSON_UNESCAPED_UNICODE),
'display_config_json' => json_encode([
'type' => 'table',
'columns' => ['facilities.name_ar', 'reservation_count', 'total_revenue'],
'chart' => ['type' => 'bar', 'x' => 'facilities.name_ar', 'y' => 'total_revenue'],
], JSON_UNESCAPED_UNICODE),
'sort_order' => 6,
'report_code' => 'RPT_RESERVATIONS',
'name_ar' => 'الحجوزات',
'name_en' => 'Reservations',
'description_ar' => 'عدد الحجوزات والإيرادات حسب الملعب والفترة الزمنية',
'category' => 'sports',
'required_permission' => 'report.view_sports_financial',
],
[
'report_code' => 'RPT_RENTAL_REVENUE',
'name_ar' => 'إيرادات التأجير',
'name_en' => 'Rental Revenue',
'description_ar' => 'إيرادات عقود التأجير حسب الجهة والملعب والفترة الزمنية',
'category' => 'sports',
'query_config_json' => json_encode([
'table' => 'rental_contracts',
'joins' => [
['table' => 'facilities', 'on' => 'rental_contracts.facility_id = facilities.id', 'type' => 'INNER'],
],
'aggregations' => [
['function' => 'COUNT', 'field' => 'rental_contracts.id', 'alias' => 'contract_count'],
['function' => 'SUM', 'field' => 'rental_contracts.total_amount', 'alias' => 'total_revenue'],
],
'group_by' => ['rental_contracts.entity_name', 'facilities.id', 'facilities.name_ar'],
], JSON_UNESCAPED_UNICODE),
'parameters_json' => json_encode([
['name' => 'date_from', 'type' => 'date', 'label_ar' => 'من تاريخ', 'required' => false],
['name' => 'date_to', 'type' => 'date', 'label_ar' => 'إلى تاريخ', 'required' => false],
['name' => 'facility_id', 'type' => 'select', 'label_ar' => 'الملعب', 'required' => false, 'data_source' => 'facilities'],
['name' => 'entity_name', 'type' => 'text', 'label_ar' => 'اسم الجهة', 'required' => false],
], JSON_UNESCAPED_UNICODE),
'display_config_json' => json_encode([
'type' => 'table',
'columns' => ['rental_contracts.entity_name', 'facilities.name_ar', 'contract_count', 'total_revenue'],
], JSON_UNESCAPED_UNICODE),
'sort_order' => 7,
'report_code' => 'RPT_RENTAL_REVENUE',
'name_ar' => 'إيرادات التأجير',
'name_en' => 'Rental Revenue',
'description_ar' => 'إيرادات عقود التأجير حسب الجهة والملعب والفترة الزمنية',
'category' => 'sports',
'required_permission' => 'report.view_sports_financial',
],
[
'report_code' => 'RPT_SUB_COLLECTION',
'name_ar' => 'تحصيل الاشتراكات',
'name_en' => 'Subscription Collection',
'description_ar' => 'نسب تحصيل اشتراكات الأنشطة الرياضية: المسدد والمعلق والمتأخر',
'category' => 'sports',
'query_config_json' => json_encode([
'table' => 'activity_subscriptions',
'joins' => [
['table' => 'players', 'on' => 'activity_subscriptions.player_id = players.id', 'type' => 'INNER'],
],
'aggregations' => [
['function' => 'COUNT', 'field' => 'activity_subscriptions.id', 'alias' => 'total_subscriptions'],
['function' => 'SUM', 'field' => 'CASE WHEN activity_subscriptions.status = \'paid\' THEN 1 ELSE 0 END', 'alias' => 'paid_count'],
['function' => 'SUM', 'field' => 'CASE WHEN activity_subscriptions.status = \'pending\' THEN 1 ELSE 0 END', 'alias' => 'pending_count'],
['function' => 'SUM', 'field' => 'CASE WHEN activity_subscriptions.status = \'overdue\' THEN 1 ELSE 0 END', 'alias' => 'overdue_count'],
['function' => 'SUM', 'field' => 'activity_subscriptions.amount', 'alias' => 'total_amount'],
['function' => 'SUM', 'field' => 'CASE WHEN activity_subscriptions.status = \'paid\' THEN activity_subscriptions.amount ELSE 0 END', 'alias' => 'collected_amount'],
],
'group_by' => [],
], JSON_UNESCAPED_UNICODE),
'parameters_json' => json_encode([
['name' => 'date_from', 'type' => 'date', 'label_ar' => 'من تاريخ', 'required' => false],
['name' => 'date_to', 'type' => 'date', 'label_ar' => 'إلى تاريخ', 'required' => false],
['name' => 'discipline_id', 'type' => 'select', 'label_ar' => 'النشاط الرياضي', 'required' => false, 'data_source' => 'sport_disciplines'],
['name' => 'academy_id', 'type' => 'select', 'label_ar' => 'الأكاديمية', 'required' => false, 'data_source' => 'academies'],
], JSON_UNESCAPED_UNICODE),
'display_config_json' => json_encode([
'type' => 'summary_cards',
'columns' => ['total_subscriptions', 'paid_count', 'pending_count', 'overdue_count', 'total_amount', 'collected_amount'],
'chart' => ['type' => 'pie', 'labels' => ['paid_count', 'pending_count', 'overdue_count']],
], JSON_UNESCAPED_UNICODE),
'sort_order' => 8,
'report_code' => 'RPT_SUB_COLLECTION',
'name_ar' => 'تحصيل الاشتراكات',
'name_en' => 'Subscription Collection',
'description_ar' => 'نسب تحصيل اشتراكات الأنشطة الرياضية: المسدد والمعلق والمتأخر',
'category' => 'sports',
'required_permission' => 'report.view_sports_financial',
],
[
'report_code' => 'RPT_SUB_OVERDUE',
'name_ar' => 'المتأخرات',
'name_en' => 'Overdue Subscriptions',
'description_ar' => 'قائمة الاشتراكات المتأخرة مع المبالغ وعدد أيام التأخر',
'category' => 'sports',
'query_config_json' => json_encode([
'table' => 'activity_subscriptions',
'joins' => [
['table' => 'players', 'on' => 'activity_subscriptions.player_id = players.id', 'type' => 'INNER'],
],
'aggregations' => [],
'group_by' => [],
'where' => [
['field' => 'activity_subscriptions.status', 'operator' => '=', 'value' => 'overdue'],
],
'computed_fields' => [
['alias' => 'days_overdue', 'expression' => 'DATEDIFF(CURDATE(), activity_subscriptions.due_date)'],
],
], JSON_UNESCAPED_UNICODE),
'parameters_json' => json_encode([
['name' => 'date_from', 'type' => 'date', 'label_ar' => 'من تاريخ', 'required' => false],
['name' => 'date_to', 'type' => 'date', 'label_ar' => 'إلى تاريخ', 'required' => false],
['name' => 'discipline_id', 'type' => 'select', 'label_ar' => 'النشاط الرياضي', 'required' => false, 'data_source' => 'sport_disciplines'],
['name' => 'academy_id', 'type' => 'select', 'label_ar' => 'الأكاديمية', 'required' => false, 'data_source' => 'academies'],
['name' => 'min_days_overdue', 'type' => 'number', 'label_ar' => 'الحد الأدنى لأيام التأخر', 'required' => false],
], JSON_UNESCAPED_UNICODE),
'display_config_json' => json_encode([
'type' => 'table',
'columns' => ['players.full_name_ar', 'activity_subscriptions.month', 'activity_subscriptions.amount', 'activity_subscriptions.due_date', 'days_overdue'],
], JSON_UNESCAPED_UNICODE),
'sort_order' => 9,
'report_code' => 'RPT_SUB_OVERDUE',
'name_ar' => 'المتأخرات',
'name_en' => 'Overdue Subscriptions',
'description_ar' => 'قائمة الاشتراكات المتأخرة مع المبالغ وعدد أيام التأخر',
'category' => 'sports',
'required_permission' => 'report.view_sports_financial',
],
[
'report_code' => 'RPT_ATTENDANCE',
'name_ar' => 'الحضور',
'name_en' => 'Attendance',
'description_ar' => 'نسب الحضور لكل أكاديمية ونشاط رياضي حسب الشهر',
'category' => 'sports',
'query_config_json' => json_encode([
'table' => 'attendance_records',
'joins' => [
['table' => 'players', 'on' => 'attendance_records.player_id = players.id', 'type' => 'INNER'],
['table' => 'academies', 'on' => 'attendance_records.academy_id = academies.id', 'type' => 'LEFT'],
['table' => 'sport_disciplines', 'on' => 'attendance_records.discipline_id = sport_disciplines.id', 'type' => 'LEFT'],
],
'aggregations' => [
['function' => 'COUNT', 'field' => 'attendance_records.id', 'alias' => 'total_records'],
['function' => 'SUM', 'field' => 'CASE WHEN attendance_records.status = \'present\' THEN 1 ELSE 0 END', 'alias' => 'present_count'],
['function' => 'SUM', 'field' => 'CASE WHEN attendance_records.status = \'absent\' THEN 1 ELSE 0 END', 'alias' => 'absent_count'],
],
'group_by' => ['academies.id', 'academies.name_ar', 'sport_disciplines.id', 'sport_disciplines.name_ar', 'MONTH(attendance_records.attendance_date)'],
], JSON_UNESCAPED_UNICODE),
'parameters_json' => json_encode([
['name' => 'date_from', 'type' => 'date', 'label_ar' => 'من تاريخ', 'required' => false],
['name' => 'date_to', 'type' => 'date', 'label_ar' => 'إلى تاريخ', 'required' => false],
['name' => 'discipline_id', 'type' => 'select', 'label_ar' => 'النشاط الرياضي', 'required' => false, 'data_source' => 'sport_disciplines'],
['name' => 'academy_id', 'type' => 'select', 'label_ar' => 'الأكاديمية', 'required' => false, 'data_source' => 'academies'],
], JSON_UNESCAPED_UNICODE),
'display_config_json' => json_encode([
'type' => 'table',
'columns' => ['academies.name_ar', 'sport_disciplines.name_ar', 'month', 'total_records', 'present_count', 'absent_count'],
'chart' => ['type' => 'line', 'x' => 'month', 'y' => ['present_count', 'absent_count']],
], JSON_UNESCAPED_UNICODE),
'sort_order' => 10,
'report_code' => 'RPT_ATTENDANCE',
'name_ar' => 'الحضور',
'name_en' => 'Attendance',
'description_ar' => 'نسب الحضور لكل أكاديمية ونشاط رياضي حسب الشهر',
'category' => 'sports',
'required_permission' => 'report.view_sports',
],
[
'report_code' => 'RPT_SPORTS_QUARTERLY',
'name_ar' => 'التقرير الربع سنوي',
'name_en' => 'Sports Quarterly Report',
'description_ar' => 'تقرير ربع سنوي شامل يجمع بيانات اللاعبين والإيرادات والحضور',
'category' => 'sports',
'query_config_json' => json_encode([
'table' => 'players',
'joins' => [
['table' => 'activity_subscriptions', 'on' => 'players.id = activity_subscriptions.player_id', 'type' => 'LEFT'],
['table' => 'attendance_records', 'on' => 'players.id = attendance_records.player_id', 'type' => 'LEFT'],
['table' => 'reservations', 'on' => 'reservations.facility_id IS NOT NULL', 'type' => 'LEFT'],
],
'aggregations' => [
['function' => 'COUNT', 'field' => 'DISTINCT players.id', 'alias' => 'total_players'],
['function' => 'SUM', 'field' => 'activity_subscriptions.amount', 'alias' => 'total_subscription_revenue'],
['function' => 'COUNT', 'field' => 'attendance_records.id', 'alias' => 'total_attendance_records'],
['function' => 'COUNT', 'field' => 'DISTINCT reservations.id', 'alias' => 'total_reservations'],
],
'group_by' => ['QUARTER(players.created_at)', 'YEAR(players.created_at)'],
], JSON_UNESCAPED_UNICODE),
'parameters_json' => json_encode([
['name' => 'year', 'type' => 'number', 'label_ar' => 'السنة', 'required' => true],
['name' => 'quarter', 'type' => 'select', 'label_ar' => 'الربع', 'required' => true, 'options' => [1, 2, 3, 4]],
], JSON_UNESCAPED_UNICODE),
'display_config_json' => json_encode([
'type' => 'composite',
'sections' => [
['title_ar' => 'ملخص اللاعبين', 'type' => 'summary_cards', 'columns' => ['total_players']],
['title_ar' => 'الإيرادات', 'type' => 'summary_cards', 'columns' => ['total_subscription_revenue']],
['title_ar' => 'الحضور', 'type' => 'summary_cards', 'columns' => ['total_attendance_records']],
['title_ar' => 'الحجوزات', 'type' => 'summary_cards', 'columns' => ['total_reservations']],
],
], JSON_UNESCAPED_UNICODE),
'sort_order' => 11,
'report_code' => 'RPT_SPORTS_QUARTERLY',
'name_ar' => 'التقرير الربع سنوي',
'name_en' => 'Sports Quarterly Report',
'description_ar' => 'تقرير ربع سنوي شامل يجمع بيانات اللاعبين والإيرادات والحضور',
'category' => 'sports',
'required_permission' => 'report.view_sports',
],
];
......@@ -361,18 +103,18 @@ return function (Database $db): void {
continue;
}
$db->insert('report_definitions', [
'report_code' => $r['report_code'],
'name_ar' => $r['name_ar'],
'name_en' => $r['name_en'],
'description_ar' => $r['description_ar'],
'category' => $r['category'],
'query_config_json' => $r['query_config_json'],
'parameters_json' => $r['parameters_json'],
'display_config_json' => $r['display_config_json'],
'is_active' => 1,
'sort_order' => $r['sort_order'],
'created_at' => $ts,
'updated_at' => $ts,
'report_code' => $r['report_code'],
'name_ar' => $r['name_ar'],
'name_en' => $r['name_en'],
'description_ar' => $r['description_ar'],
'category' => $r['category'],
'query_definition_json' => '{}',
'column_definitions_json' => '{}',
'default_filters_json' => '{}',
'required_permission' => $r['required_permission'],
'is_active' => 1,
'created_at' => $ts,
'updated_at' => $ts,
]);
}
};
......@@ -8,53 +8,67 @@ return function (Database $db): void {
$templates = [
[
'template_code' => 'SMS_SPORTS_REGISTRATION',
'name_ar' => 'تسجيل في الأنشطة الرياضية',
'event_trigger' => 'player.registered',
'message_template' => 'مرحباً {player_name}، تم تسجيلك بنجاح في الأنشطة الرياضية. رقم التسجيل: {registration_serial}',
'variables_json' => json_encode(['player_name', 'registration_serial'], JSON_UNESCAPED_UNICODE),
'template_code' => 'SMS_SPORTS_REGISTRATION',
'name_ar' => 'تسجيل في الأنشطة الرياضية',
'name_en' => 'Sports Registration',
'trigger_event' => 'player.registered',
'message_template_ar' => 'مرحباً {player_name}، تم تسجيلك بنجاح في الأنشطة الرياضية. رقم التسجيل: {registration_serial}',
'message_template_en' => 'Hello {player_name}, you have been registered successfully. Registration #: {registration_serial}',
'variables_json' => json_encode(['player_name', 'registration_serial'], JSON_UNESCAPED_UNICODE),
],
[
'template_code' => 'SMS_SPORTS_SUB_DUE',
'name_ar' => 'استحقاق اشتراك النشاط الرياضي',
'event_trigger' => 'activity_sub.generated',
'message_template' => 'عزيزي {player_name}، اشتراك شهر {month} بمبلغ {amount} ج.م مستحق. الرجاء السداد قبل {due_date}',
'variables_json' => json_encode(['player_name', 'month', 'amount', 'due_date'], JSON_UNESCAPED_UNICODE),
'template_code' => 'SMS_SPORTS_SUB_DUE',
'name_ar' => 'استحقاق اشتراك النشاط الرياضي',
'name_en' => 'Activity Subscription Due',
'trigger_event' => 'activity_sub.generated',
'message_template_ar' => 'عزيزي {player_name}، اشتراك شهر {month} بمبلغ {amount} ج.م مستحق. الرجاء السداد قبل {due_date}',
'message_template_en' => 'Dear {player_name}, subscription for {month} of {amount} EGP is due. Please pay before {due_date}',
'variables_json' => json_encode(['player_name', 'month', 'amount', 'due_date'], JSON_UNESCAPED_UNICODE),
],
[
'template_code' => 'SMS_SPORTS_SUB_OVERDUE',
'name_ar' => 'تأخر اشتراك النشاط الرياضي',
'event_trigger' => 'activity_sub.overdue',
'message_template' => 'تنبيه: اشتراك {player_name} لشهر {month} متأخر. المبلغ: {amount} ج.م. سيتم إيقاف الكارنيه في حالة عدم السداد',
'variables_json' => json_encode(['player_name', 'month', 'amount'], JSON_UNESCAPED_UNICODE),
'template_code' => 'SMS_SPORTS_SUB_OVERDUE',
'name_ar' => 'تأخر اشتراك النشاط الرياضي',
'name_en' => 'Activity Subscription Overdue',
'trigger_event' => 'activity_sub.overdue',
'message_template_ar' => 'تنبيه: اشتراك {player_name} لشهر {month} متأخر. المبلغ: {amount} ج.م. سيتم إيقاف الكارنيه في حالة عدم السداد',
'message_template_en' => 'Alert: {player_name} subscription for {month} is overdue. Amount: {amount} EGP. Card will be suspended if unpaid',
'variables_json' => json_encode(['player_name', 'month', 'amount'], JSON_UNESCAPED_UNICODE),
],
[
'template_code' => 'SMS_SPORTS_CARD_REVOKED',
'name_ar' => 'إيقاف كارنيه النشاط الرياضي',
'event_trigger' => 'player.card_revoked',
'message_template' => 'تم إيقاف كارنيه النشاط الرياضي للاعب {player_name} بسبب عدم سداد الاشتراك. للاستفسار: اتصل بإدارة النادي',
'variables_json' => json_encode(['player_name'], JSON_UNESCAPED_UNICODE),
'template_code' => 'SMS_SPORTS_CARD_REVOKED',
'name_ar' => 'إيقاف كارنيه النشاط الرياضي',
'name_en' => 'Activity Card Revoked',
'trigger_event' => 'player.card_revoked',
'message_template_ar' => 'تم إيقاف كارنيه النشاط الرياضي للاعب {player_name} بسبب عدم سداد الاشتراك. للاستفسار: اتصل بإدارة النادي',
'message_template_en' => 'Activity card for {player_name} has been revoked due to unpaid subscription. Contact club administration for inquiries',
'variables_json' => json_encode(['player_name'], JSON_UNESCAPED_UNICODE),
],
[
'template_code' => 'SMS_RESERVATION_CONFIRMED',
'name_ar' => 'تأكيد حجز ملعب',
'event_trigger' => 'reservation.confirmed',
'message_template' => 'تم تأكيد حجز {facility_name} بتاريخ {date} من {start_time} إلى {end_time}. رقم الحجز: {reservation_number}',
'variables_json' => json_encode(['facility_name', 'date', 'start_time', 'end_time', 'reservation_number'], JSON_UNESCAPED_UNICODE),
'template_code' => 'SMS_RESERVATION_CONFIRMED',
'name_ar' => 'تأكيد حجز ملعب',
'name_en' => 'Reservation Confirmed',
'trigger_event' => 'reservation.confirmed',
'message_template_ar' => 'تم تأكيد حجز {facility_name} بتاريخ {date} من {start_time} إلى {end_time}. رقم الحجز: {reservation_number}',
'message_template_en' => 'Reservation confirmed for {facility_name} on {date} from {start_time} to {end_time}. Ref: {reservation_number}',
'variables_json' => json_encode(['facility_name', 'date', 'start_time', 'end_time', 'reservation_number'], JSON_UNESCAPED_UNICODE),
],
[
'template_code' => 'SMS_RESERVATION_REMINDER',
'name_ar' => 'تذكير بحجز ملعب',
'event_trigger' => 'reservation.reminder',
'message_template' => 'تذكير: لديك حجز {facility_name} غداً {date} الساعة {start_time}. رقم الحجز: {reservation_number}',
'variables_json' => json_encode(['facility_name', 'date', 'start_time', 'reservation_number'], JSON_UNESCAPED_UNICODE),
'template_code' => 'SMS_RESERVATION_REMINDER',
'name_ar' => 'تذكير بحجز ملعب',
'name_en' => 'Reservation Reminder',
'trigger_event' => 'reservation.reminder',
'message_template_ar' => 'تذكير: لديك حجز {facility_name} غداً {date} الساعة {start_time}. رقم الحجز: {reservation_number}',
'message_template_en' => 'Reminder: You have a reservation at {facility_name} tomorrow {date} at {start_time}. Ref: {reservation_number}',
'variables_json' => json_encode(['facility_name', 'date', 'start_time', 'reservation_number'], JSON_UNESCAPED_UNICODE),
],
[
'template_code' => 'SMS_MEDICAL_EXPIRY',
'name_ar' => 'تنبيه انتهاء الشهادة الطبية',
'event_trigger' => 'player.medical_expiry',
'message_template' => 'تنبيه: شهادتك الطبية ستنتهي بتاريخ {expiry_date}. يرجى تجديدها لاستمرار التسجيل في الأنشطة الرياضية',
'variables_json' => json_encode(['player_name', 'expiry_date'], JSON_UNESCAPED_UNICODE),
'template_code' => 'SMS_MEDICAL_EXPIRY',
'name_ar' => 'تنبيه انتهاء الشهادة الطبية',
'name_en' => 'Medical Certificate Expiry',
'trigger_event' => 'player.medical_expiry',
'message_template_ar' => 'تنبيه: شهادتك الطبية ستنتهي بتاريخ {expiry_date}. يرجى تجديدها لاستمرار التسجيل في الأنشطة الرياضية',
'message_template_en' => 'Alert: Your medical certificate expires on {expiry_date}. Please renew to continue sports activities',
'variables_json' => json_encode(['player_name', 'expiry_date'], JSON_UNESCAPED_UNICODE),
],
];
......@@ -64,14 +78,16 @@ return function (Database $db): void {
continue;
}
$db->insert('sms_templates', [
'template_code' => $t['template_code'],
'name_ar' => $t['name_ar'],
'event_trigger' => $t['event_trigger'],
'message_template' => $t['message_template'],
'variables_json' => $t['variables_json'],
'is_active' => 1,
'created_at' => $ts,
'updated_at' => $ts,
'template_code' => $t['template_code'],
'name_ar' => $t['name_ar'],
'name_en' => $t['name_en'],
'trigger_event' => $t['trigger_event'],
'message_template_ar' => $t['message_template_ar'],
'message_template_en' => $t['message_template_en'],
'variables_json' => $t['variables_json'],
'is_active' => 1,
'created_at' => $ts,
'updated_at' => $ts,
]);
}
};
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