Commit 4b76c4b3 authored by Mahmoud Aglan's avatar Mahmoud Aglan

Enhance billing display with full calculation breakdowns for all membership types

- Foreign bill: branch-specific fee lookup, exchange rate conversion, EGP display
- Seasonal bill: duration, nationality, dates, base amount, discounts, VAT, family members
- Sports bill: improved breakdown with separator and total line
- Form fee: add in_queue status to bill item
- Controller: type-specific payment types, validation whitelist, descriptions
- View: conditional installment option, working-only family links, type-specific labels
Co-Authored-By: 's avatarClaude Opus 4.6 <noreply@anthropic.com>
parent 39382feb
......@@ -217,7 +217,10 @@ class MemberController extends Controller
\App\Modules\Members\Services\MembershipPaymentGuard::reconcile((int) $id);
$bill = BillingService::getMemberBill((int) $id);
$formFilled = ($member->qualification_id !== null && $member->qualification_id > 0);
$membershipType = $member->membership_type ?? 'working';
$formFilled = ($membershipType !== 'working')
? true
: ($member->qualification_id !== null && $member->qualification_id > 0);
$specialDiscount = null;
if ($member->special_discount_id) {
......@@ -357,6 +360,11 @@ class MemberController extends Controller
$paymentType = trim((string) $request->post('payment_type', ''));
$amount = trim((string) $request->post('amount', '0'));
$validPaymentTypes = ['membership_fee', 'down_payment', 'foreign_membership_fee', 'sports_membership_fee', 'seasonal_fee'];
if (!in_array($paymentType, $validPaymentTypes, true)) {
return $this->redirect('/members/' . $id)->withError('نوع الدفع غير صالح');
}
if (bccomp($amount, '0.01', 2) < 0) return $this->redirect('/members/' . $id)->withError('المبلغ غير صالح');
$months = ($paymentType === 'down_payment') ? min(30, max(1, (int) $request->post('installment_months', 30))) : null;
......@@ -387,13 +395,22 @@ class MemberController extends Controller
$notesData = ['fee_breakdown' => $breakdown];
if ($months) $notesData['installment_months'] = $months;
$typeDescriptions = [
'down_payment' => 'مقدم تقسيط',
'membership_fee' => 'قيمة العضوية',
'foreign_membership_fee' => 'رسوم عضوية أجنبية',
'sports_membership_fee' => 'رسوم عضوية رياضية',
'seasonal_fee' => 'رسوم عضوية موسمية',
];
$descriptionAr = ($typeDescriptions[$paymentType] ?? 'قيمة العضوية') . ' — استمارة ' . ($member->form_number ?? '');
$result = PaymentRequestService::createRequest([
'member_id' => (int) $id,
'amount' => $amount,
'payment_type' => $paymentType,
'related_entity_type' => 'members',
'related_entity_id' => (int) $id,
'description_ar' => ($paymentType === 'down_payment' ? 'مقدم تقسيط' : 'قيمة العضوية') . ' — استمارة ' . ($member->form_number ?? ''),
'description_ar' => $descriptionAr,
'notes' => json_encode($notesData, JSON_UNESCAPED_UNICODE),
]);
if (!$result['success']) return $this->redirect('/members/' . $id)->withError($result['error']);
......@@ -416,7 +433,7 @@ class MemberController extends Controller
// Block if a combined membership payment is already pending
$hasCombined = $db->selectOne(
"SELECT id FROM payment_requests WHERE member_id = ? AND payment_type IN ('membership_fee','down_payment') AND status IN ('pending','processing') AND is_voided = 0 LIMIT 1",
"SELECT id FROM payment_requests WHERE member_id = ? AND payment_type IN ('membership_fee','down_payment','foreign_membership_fee','sports_membership_fee','seasonal_fee') AND status IN ('pending','processing') AND is_voided = 0 LIMIT 1",
[(int) $id]
);
if ($hasCombined) {
......
......@@ -83,18 +83,20 @@ $canEdit = can('member.edit') && (!$isLocked || ($isSuperAdmin ?? false));
<div class="card" style="margin-bottom:20px;padding:25px;background:#F0FDF4;border:2px solid #059669;">
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:12px;">
<div>
<h3 style="color:#059669;margin:0 0 8px;">✅ تم دفع رسوم الاستمارة — الآن قم بملء البيانات وإضافة الأسرة</h3>
<p style="color:#6B7280;margin:0;font-size:14px;">استمارة رقم <strong><?= e($member->form_number) ?></strong> — يمكنك إضافة أفراد الأسرة الآن بدون رسوم إضافية</p>
<h3 style="color:#059669;margin:0 0 8px;">✅ تم دفع رسوم الاستمارة — الآن قم بملء البيانات<?= ($member->membership_type ?? 'working') === 'working' ? ' وإضافة الأسرة' : '' ?></h3>
<p style="color:#6B7280;margin:0;font-size:14px;">استمارة رقم <strong><?= e($member->form_number) ?></strong><?= ($member->membership_type ?? 'working') === 'working' ? ' — يمكنك إضافة أفراد الأسرة الآن بدون رسوم إضافية' : '' ?></p>
</div>
<?php if (can('member.fill_form')): ?>
<a href="/members/<?= (int) $member->id ?>/fill-form" class="btn btn-primary" style="padding:15px 30px;font-size:18px;">📝 ملء الاستمارة</a>
<?php endif; ?>
</div>
<?php if (($member->membership_type ?? 'working') === 'working'): ?>
<div style="display:flex;gap:8px;flex-wrap:wrap;padding-top:12px;border-top:1px solid #A7F3D0;">
<a href="/members/<?= (int) $member->id ?>/spouses/create" class="btn btn-sm btn-outline">💍 إضافة زوج/ة</a>
<a href="/members/<?= (int) $member->id ?>/children/create" class="btn btn-sm btn-outline">👶 إضافة ابن/ة</a>
<a href="/members/<?= (int) $member->id ?>/temporary/create" class="btn btn-sm btn-outline">👤 إضافة عضو مؤقت</a>
</div>
<?php endif; ?>
</div>
<?php endif; ?>
......@@ -228,13 +230,15 @@ $canEdit = can('member.edit') && (!$isLocked || ($isSuperAdmin ?? false));
</table>
</div>
<!-- Add Family Before Paying -->
<!-- Add Family Before Paying (working type only) -->
<?php if (($member->membership_type ?? 'working') === 'working'): ?>
<div style="padding:0 20px 15px;display:flex;gap:10px;flex-wrap:wrap;border-top:1px solid #E5E7EB;padding-top:15px;">
<span style="color:#6B7280;font-size:13px;padding:8px 0;">أضف أفراد الأسرة (بدون رسوم استمارة إضافية):</span>
<a href="/members/<?= (int) $member->id ?>/spouses/create" class="btn btn-sm btn-outline">💍 إضافة زوج/ة</a>
<a href="/members/<?= (int) $member->id ?>/children/create" class="btn btn-sm btn-outline">👶 إضافة ابن/ابنة</a>
<a href="/members/<?= (int) $member->id ?>/temporary/create" class="btn btn-sm btn-outline">👤 عضو مؤقت</a>
</div>
<?php endif; ?>
<!-- Special Discount Section -->
<?php if (!empty($availableDiscounts) && in_array($member->status, ['accepted', 'payment_pending']) && empty($pendingMembership)): ?>
......@@ -302,9 +306,18 @@ $canEdit = can('member.edit') && (!$isLocked || ($isSuperAdmin ?? false));
<?php endif; ?>
</div>
<?php else: ?>
<?php
$paymentTypeForType = match ($member->membership_type ?? 'working') {
'foreign' => 'foreign_membership_fee',
'sports' => 'sports_membership_fee',
'seasonal' => 'seasonal_fee',
default => 'membership_fee',
};
$allowInstallment = in_array($member->membership_type ?? 'working', ['working', 'sports'], true);
?>
<div style="padding:20px;background:#FFF7ED;border-top:2px solid #F59E0B;">
<h4 style="margin:0 0 15px;color:#D97706;">&#x1f4b0; اختر طريقة السداد وأرسل للخزينة</h4>
<div style="display:grid;grid-template-columns:1fr 1fr;gap:20px;">
<div style="display:grid;grid-template-columns:<?= $allowInstallment ? '1fr 1fr' : '1fr' ?>;gap:20px;">
<!-- Cash Full -->
<div style="background:#fff;border:2px solid #059669;border-radius:12px;padding:20px;">
<h5 style="margin:0 0 10px;color:#059669;">&#x1f4b5; كاش كامل</h5>
......@@ -312,11 +325,12 @@ $canEdit = can('member.edit') && (!$isLocked || ($isSuperAdmin ?? false));
<div style="font-size:24px;font-weight:700;color:#059669;margin-bottom:15px;"><?= money($bill['total_pending']) ?></div>
<form method="POST" action="/members/<?= (int) $member->id ?>/pay-membership">
<?= csrf_field() ?>
<input type="hidden" name="payment_type" value="membership_fee">
<input type="hidden" name="payment_type" value="<?= e($paymentTypeForType) ?>">
<input type="hidden" name="amount" value="<?= e($bill['total_pending']) ?>">
<button type="submit" class="btn btn-primary" style="width:100%;background:#D97706;border-color:#D97706;" onclick="return confirm('إرسال طلب دفع <?= money($bill['total_pending']) ?> للخزينة؟')">&#x1f4e4; إرسال للخزينة</button>
</form>
</div>
<?php if ($allowInstallment): ?>
<!-- Installment -->
<div style="background:#fff;border:2px solid #0284C7;border-radius:12px;padding:20px;">
<h5 style="margin:0 0 10px;color:#0284C7;">&#x1f4c5; تقسيط</h5>
......@@ -348,6 +362,7 @@ $canEdit = can('member.edit') && (!$isLocked || ($isSuperAdmin ?? false));
<button type="submit" class="btn btn-primary" style="width:100%;background:#D97706;border-color:#D97706;" onclick="return confirm('إرسال طلب تقسيط للخزينة؟')">&#x1f4e4; إرسال للخزينة</button>
</form>
</div>
<?php endif; ?>
</div>
<div style="margin-top:15px;padding:10px;background:#FEF2F2;border-radius:8px;font-size:12px;color:#DC2626;">
&#x26a0;&#xfe0f; مهلة السداد: 15 يوم من تاريخ القبول — بعدها تنتهي الاستمارة
......@@ -357,7 +372,9 @@ $canEdit = can('member.edit') && (!$isLocked || ($isSuperAdmin ?? false));
<?php elseif ($formFilled && in_array($member->status, ['under_review', 'interview_scheduled'])): ?>
<div style="padding:20px;background:#EFF6FF;border-top:2px solid #0284C7;">
<p style="margin:0;color:#0284C7;font-size:14px;">📋 الفاتورة جاهزة — في انتظار قرار مجلس الأمناء قبل السداد</p>
<?php if (($member->membership_type ?? 'working') === 'working'): ?>
<p style="margin:5px 0 0;color:#6B7280;font-size:13px;">يمكنك إضافة أفراد الأسرة الآن وستُضاف رسومهم تلقائياً</p>
<?php endif; ?>
</div>
<?php endif; ?>
</div>
......@@ -492,16 +509,30 @@ if ($bill['form_fee_paid'] && !$formFilled) {
$missingSteps[] = ['icon' => '&#x2705;', 'text' => 'الاستمارة مكتملة', 'color' => '#059669', 'done' => true];
}
if ($formFilled && !$bill['membership_paid'] && !in_array($member->status, ['active'], true)) {
$membershipFeeLabel = match ($member->membership_type ?? 'working') {
'foreign' => 'رسوم العضوية الأجنبية',
'sports' => 'رسوم العضوية الرياضية',
'seasonal' => 'رسوم العضوية الموسمية',
'honorary' => 'تفعيل العضوية الشرفية',
default => 'رسوم العضوية',
};
if (($member->membership_type ?? 'working') === 'honorary') {
if ($member->status !== 'active') {
$missingSteps[] = ['icon' => '&#x1f4cb;', 'text' => 'في انتظار تسجيل بيانات العضوية الشرفية والتفعيل', 'color' => '#3B82F6', 'done' => false];
} else {
$missingSteps[] = ['icon' => '&#x2705;', 'text' => $membershipFeeLabel, 'color' => '#059669', 'done' => true];
}
} elseif ($formFilled && !$bill['membership_paid'] && !in_array($member->status, ['active'], true)) {
if (!empty($bill['membership_pending']) || !empty($pendingMembership)) {
$missingSteps[] = ['icon' => '&#x1f4b3;', 'text' => 'رسوم العضوية في انتظار الخزينة', 'color' => '#D97706', 'done' => false];
$missingSteps[] = ['icon' => '&#x1f4b3;', 'text' => $membershipFeeLabel . ' في انتظار الخزينة', 'color' => '#D97706', 'done' => false];
} elseif (in_array($member->status, ['accepted', 'payment_pending'], true)) {
$missingSteps[] = ['icon' => '&#x23f3;', 'text' => 'لم يتم سداد رسوم العضوية', 'color' => '#DC2626', 'done' => false];
$missingSteps[] = ['icon' => '&#x23f3;', 'text' => 'لم يتم سداد ' . $membershipFeeLabel, 'color' => '#DC2626', 'done' => false];
} elseif (in_array($member->status, ['under_review', 'interview_scheduled'], true)) {
$missingSteps[] = ['icon' => '&#x1f4cb;', 'text' => 'في انتظار قرار مجلس الأمناء', 'color' => '#3B82F6', 'done' => false];
}
} elseif ($bill['membership_paid'] || $member->status === 'active') {
$missingSteps[] = ['icon' => '&#x2705;', 'text' => 'رسوم العضوية', 'color' => '#059669', 'done' => true];
$missingSteps[] = ['icon' => '&#x2705;', 'text' => $membershipFeeLabel, 'color' => '#059669', 'done' => true];
}
foreach ($bill['items'] as $item) {
......
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