Commit 28ea8431 authored by DevPilot's avatar DevPilot

fix(sports-activity): 4 real bugs found while documenting core screens

- تقرير الماليات (SaFinanceReportService): الـ switch بتاع تكلفة المدربين
  كان بيقارن بقيم زي 'monthly'/'hourly' مش موجودة في enum الحقيقي
  (monthly_fixed/per_session/per_player/hybrid/salary) — يعني أي مدرب
  مش per_session كان بيتحسب تكلفته صفر، وصافي الربح المعروض في التقرير
  كان مبالغ فيه بشكل منهجي. اتصلحت لتغطي الخمس نماذج كلهم، بنفس صيغة
  الحساب المستخدمة فعلًا في Coaches\Services\CoachPaymentService
  (مدرب الأكاديمية 'salary' متعمّد يفضل صفر — أجره بيتحسب من خلال تسوية
  عقد الأكاديمية مش كبند تكلفة مباشر).
- FacilityUnit::$fillable ما كانش فيه expected_capacity، فالحقل كان
  بيتقبل في الفورم ويتفلتر بصمت قبل ما يوصل لقاعدة البيانات.
- EnrollmentService::forceEnroll() كان بيثبّت is_full=1 دايمًا بدل ما
  يحسبه — تجاوز واحد في مجموعة سعتها 20 كان بيقفل التسجيل العادي على
  الـ 19 مكان الفاضيين اللي بعده.
- زرار سحب اللاعب من المجموعة كان بيتفعّل بصلاحية sa.group.enroll بينما
  الـ route نفسه محتاج sa.group.manage — تضارب بين الاتنين حسب الدور.
parent 5d693197
......@@ -16,8 +16,8 @@ class FacilityUnit extends Model
protected static array $fillable = [
'facility_id', 'code', 'name_ar', 'name_en', 'unit_type',
'booking_mode', 'max_capacity', 'sort_order', 'dimensions_json',
'config_json', 'is_active',
'booking_mode', 'max_capacity', 'expected_capacity', 'sort_order',
'dimensions_json', 'config_json', 'is_active',
];
public static function getUnitTypeOptions(): array
......
......@@ -311,10 +311,14 @@ final class EnrollmentService
], 'id = ?', [$enrollmentId]);
}
// كان بيتثبّت على 1 دايمًا — يعني أي تجاوز سعة كان بيقفل التسجيل
// العادي بعد كده حتى لو المجموعة لسه فاضية فعليًا (تجاوز واحد
// في مجموعة سعتها 20 كان بيمنع الـ 19 تسجيل العاديين اللي بعده).
$newCount = (int) $group['current_count'] + 1;
$isFull = $newCount >= (int) $group['max_capacity'] ? 1 : 0;
$db->update('sa_groups', [
'current_count' => $newCount,
'is_full' => 1,
'is_full' => $isFull,
'updated_at' => date('Y-m-d H:i:s'),
], 'id = ?', [$groupId]);
......
......@@ -180,31 +180,49 @@ final class SaFinanceReportService
foreach ($coaches as $coach) {
$coachCost = '0.00';
// عدد الجلسات وعدد اللاعبين اللي اتحضّروا فعلًا في الفترة — مطلوبين
// لأكتر من نموذج دفع، فبنحسبهم مرة واحدة قبل الـ switch.
$sessionCount = $totalPlayersServed = 0;
if (in_array($coach['payment_model'], ['per_session', 'per_player', 'hybrid'], true)) {
$stats = $db->selectOne(
"SELECT COUNT(DISTINCT a.booking_id, a.attendance_date) AS sessions,
COUNT(DISTINCT CASE WHEN a.status = 'present' THEN a.player_id END) AS players
FROM sa_attendance a
INNER JOIN sa_bookings bk ON bk.id = a.booking_id
WHERE bk.coach_id = ? AND a.attendance_date BETWEEN ? AND ?",
[(int) $coach['id'], $from, $to]
);
$sessionCount = (int) ($stats['sessions'] ?? 0);
$totalPlayersServed = (int) ($stats['players'] ?? 0);
}
switch ($coach['payment_model']) {
case 'monthly':
case 'monthly_fixed':
$coachCost = bcmul((string) ($coach['monthly_rate'] ?? '0'), (string) $monthsInRange, 2);
break;
case 'per_session':
$sessionCount = $db->selectOne(
"SELECT COUNT(DISTINCT a.booking_id, a.attendance_date) AS cnt
FROM sa_attendance a
INNER JOIN sa_bookings bk ON bk.id = a.booking_id
WHERE bk.coach_id = ? AND a.attendance_date BETWEEN ? AND ?",
[(int) $coach['id'], $from, $to]
);
$cnt = (int) ($sessionCount['cnt'] ?? 0);
$coachCost = bcmul((string) ($coach['session_rate'] ?? '0'), (string) $cnt, 2);
$coachCost = bcmul((string) ($coach['session_rate'] ?? '0'), (string) $sessionCount, 2);
break;
case 'per_player':
// مفيش عمود سعر مخصّص لكل لاعب — بنستخدم سعر الجلسة كسعر
// لكل لاعب، وده نفس التعريف اللي بيستخدمه CoachPaymentService
// للمدربين على النظام القديم (Coaches\Services\CoachPaymentService).
$coachCost = bcmul((string) ($coach['session_rate'] ?? '0'), (string) $totalPlayersServed, 2);
break;
case 'hybrid':
// راتب أساسي شهري + بونص على أي جلسة زيادة عن ٢٠ جلسة/شهر —
// نفس تعريف "hybrid" في Coaches\Services\CoachPaymentService.
$base = bcmul((string) ($coach['monthly_rate'] ?? '0'), (string) $monthsInRange, 2);
$threshold = 20 * $monthsInRange;
$extraSessions = max(0, $sessionCount - $threshold);
$bonus = bcmul((string) ($coach['session_rate'] ?? '0'), (string) $extraSessions, 2);
$coachCost = bcadd($base, $bonus, 2);
break;
case 'hourly':
$sessionCount = $db->selectOne(
"SELECT COUNT(DISTINCT a.booking_id, a.attendance_date) AS cnt
FROM sa_attendance a
INNER JOIN sa_bookings bk ON bk.id = a.booking_id
WHERE bk.coach_id = ? AND a.attendance_date BETWEEN ? AND ?",
[(int) $coach['id'], $from, $to]
);
$cnt = (int) ($sessionCount['cnt'] ?? 0);
$coachCost = bcmul((string) ($coach['hourly_rate'] ?? '0'), (string) $cnt, 2);
case 'salary':
// مدرب أكاديمية — أجره مش بند تكلفة مباشر على النادي، بيتحسب
// ضمن تسوية عقد الأكاديمية (حصة النادي/الأكاديمية)، فمالوش
// بند هنا عن قصد.
break;
}
......
......@@ -189,7 +189,7 @@ $st = $group['status'] ?? 'active';
<i data-lucide="banknote" style="width:13px;height:13px;vertical-align:middle;margin-left:3px;"></i> الخزينة
</a>
<?php endif; ?>
<?php if (can('sa.group.enroll')): ?>
<?php if (can('sa.group.manage')): ?>
<form method="POST" action="/sa/groups/<?= (int) $group['id'] ?>/remove-player" style="display:inline;" onsubmit="return confirm('هل تريد سحب هذا اللاعب من المجموعة؟');">
<?= csrf_field() ?>
<input type="hidden" name="player_id" value="<?= (int) $pl['player_id'] ?>">
......
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