Commit 0b418d73 authored by Mahmoud Aglan's avatar Mahmoud Aglan

Custom shit

parent 89acbebd
......@@ -129,7 +129,21 @@ class DeathController extends Controller
[(int) $id]
);
if (!$case) return $this->redirect('/death')->withError('الحالة غير موجودة');
return $this->view('Death.Views.show', ['case' => $case]);
// Compute fee breakdown components
$formFee = ServicePrice::getPrice('SVC_TRANSFER_FORM', '570.00');
$annualSubBase = ServicePrice::getPrice('SVC_ANNUAL_MEMBER', '492.00');
$devFeeData = RuleEngine::get('DEVELOPMENT_FEE');
$devFee = $devFeeData['amount'] ?? '35.00';
$annualSub = bcadd($annualSubBase, $devFee, 2);
return $this->view('Death.Views.show', [
'case' => $case,
'formFee' => $formFee,
'annualSubBase' => $annualSubBase,
'devFee' => $devFee,
'annualSub' => $annualSub,
]);
}
public function pay(Request $request, string $id): Response
......
......@@ -4,15 +4,42 @@
<div class="card" style="padding:20px;margin-bottom:20px;">
<table style="width:100%;max-width:600px;font-size:14px;">
<tr><td style="padding:6px 0;color:#6B7280;width:35%;">العضو</td><td style="padding:6px 0;font-weight:600;"><?= e($case['member_name']) ?> (<?= e($case['membership_number'] ?? '—') ?>)</td></tr>
<tr><td style="padding:6px 0;color:#6B7280;">المتوفى</td><td style="padding:6px 0;"><?= $case['deceased_type'] === 'primary_member' ? 'العضو الرئيسي' : 'الزوج/ة' ?></td></tr>
<tr><td style="padding:6px 0;color:#6B7280;">المتوفى</td><td style="padding:6px 0;font-weight:600;"><?= $case['deceased_type'] === 'primary_member' ? 'العضو الرئيسي' : 'الزوج/ة' ?></td></tr>
<tr><td style="padding:6px 0;color:#6B7280;">تاريخ الوفاة</td><td style="padding:6px 0;"><?= e($case['death_date']) ?></td></tr>
<tr><td style="padding:6px 0;color:#6B7280;">شهادة الوفاة</td><td style="padding:6px 0;"><?= e($case['death_certificate_number'] ?? '—') ?></td></tr>
<tr><td style="padding:6px 0;color:#6B7280;">الرسوم (استمارة + اشتراك)</td><td style="padding:6px 0;font-weight:700;font-size:20px;color:#DC2626;"><?= money($case['fee_amount'] ?? '0') ?></td></tr>
<tr><td style="padding:6px 0;color:#6B7280;">الحالة</td><td style="padding:6px 0;font-weight:700;color:<?= match($case['status']) { 'completed' => '#059669', 'fee_paid' => '#2563EB', default => '#D97706' } ?>;"><?= match($case['status']) { 'completed' => 'مكتمل', 'fee_paid' => 'تم الدفع', default => 'مسجّل' } ?></td></tr>
<?php if ($case['transferred_to_member_id']): ?><tr><td style="padding:6px 0;color:#6B7280;">نُقلت إلى</td><td style="padding:6px 0;"><a href="/members/<?= (int) $case['transferred_to_member_id'] ?>" style="color:#0D7377;font-weight:600;">عضو #<?= (int) $case['transferred_to_member_id'] ?></a></td></tr><?php endif; ?>
</table>
</div>
<!-- Fee Breakdown -->
<div class="card" style="margin-bottom:20px;">
<div style="padding:15px 20px;border-bottom:1px solid #E5E7EB;display:flex;align-items:center;gap:8px;">
<i data-lucide="receipt" style="width:18px;height:18px;color:#D97706;"></i>
<h3 style="margin:0;color:#D97706;font-size:15px;">تفصيل الرسوم</h3>
</div>
<div style="padding:20px;">
<table style="width:100%;max-width:500px;font-size:14px;">
<tr>
<td style="padding:8px 0;color:#6B7280;">رسوم الاستمارة</td>
<td style="padding:8px 0;font-weight:600;direction:ltr;text-align:left;"><?= money($formFee ?? '0') ?></td>
</tr>
<tr>
<td style="padding:8px 0;color:#6B7280;">اشتراك سنوي</td>
<td style="padding:8px 0;font-weight:600;direction:ltr;text-align:left;"><?= money($annualSubBase ?? '0') ?></td>
</tr>
<tr>
<td style="padding:8px 0;color:#6B7280;">رسوم تنمية</td>
<td style="padding:8px 0;font-weight:600;direction:ltr;text-align:left;"><?= money($devFee ?? '0') ?></td>
</tr>
<tr style="border-top:2px solid #0D7377;">
<td style="padding:12px 0;font-weight:700;font-size:16px;">الإجمالي المطلوب</td>
<td style="padding:12px 0;font-weight:800;font-size:22px;color:#DC2626;direction:ltr;text-align:left;"><?= money($case['fee_amount'] ?? '0') ?></td>
</tr>
</table>
</div>
</div>
<?php if (!in_array($case['status'], ['completed', 'fee_paid']) && bccomp($case['fee_amount'] ?? '0', '0', 2) > 0): ?>
<div class="card" style="padding:20px;margin-bottom:20px;background:#FFF7ED;border:2px solid #F59E0B;">
<h4 style="margin:0 0 15px;color:#D97706;">💰 دفع رسوم نقل العضوية (استمارة + اشتراك سنوي)</h4>
......@@ -34,4 +61,5 @@
<button type="submit" class="btn btn-primary" onclick="return confirm('⚠ إتمام حالة الوفاة. متأكد؟')">✅ إتمام الإجراء</button>
</form>
<?php endif; ?>
<script>document.addEventListener('DOMContentLoaded', function() { if (typeof lucide !== 'undefined') lucide.createIcons(); });</script>
<?php $__template->endSection(); ?>
\ No newline at end of file
......@@ -111,12 +111,25 @@ class DivorceController extends Controller
{
$db = App::getInstance()->db();
$case = $db->selectOne(
"SELECT dc.*, m.full_name_ar as member_name, m.membership_number, s.full_name_ar as spouse_name
"SELECT dc.*, m.full_name_ar as member_name, m.membership_number, m.membership_value,
s.full_name_ar as spouse_name
FROM divorce_cases dc JOIN members m ON m.id = dc.member_id JOIN spouses s ON s.id = dc.spouse_id WHERE dc.id = ?",
[(int) $id]
);
if (!$case) return $this->redirect('/divorce')->withError('الحالة غير موجودة');
return $this->view('Divorce.Views.show', ['case' => $case]);
// Compute fee breakdown from stored data
$membershipValue = $case['membership_value'] ?? '0.00';
$feePercentage = $case['fee_percentage'] ?? '0';
$percentageFee = bcdiv(bcmul($membershipValue, (string) $feePercentage, 4), '100', 2);
$totalFee = $case['fee_amount'] ?? '0.00';
$formFee = bcsub($totalFee, $percentageFee, 2);
return $this->view('Divorce.Views.show', [
'case' => $case,
'percentageFee' => $percentageFee,
'formFee' => $formFee,
]);
}
public function pay(Request $request, string $id): Response
......
......@@ -4,16 +4,48 @@
<div class="card" style="padding:20px;margin-bottom:20px;">
<table style="width:100%;max-width:600px;font-size:14px;">
<tr><td style="padding:6px 0;color:#6B7280;width:35%;">العضو</td><td style="padding:6px 0;"><a href="/members/<?= (int) $case['member_id'] ?>" style="color:#0D7377;font-weight:600;"><?= e($case['member_name']) ?></a></td></tr>
<tr><td style="padding:6px 0;color:#6B7280;">الزوج/ة</td><td style="padding:6px 0;"><?= e($case['spouse_name']) ?></td></tr>
<tr><td style="padding:6px 0;color:#6B7280;">الزوج/ة</td><td style="padding:6px 0;font-weight:600;"><?= e($case['spouse_name']) ?></td></tr>
<tr><td style="padding:6px 0;color:#6B7280;">تاريخ الطلاق</td><td style="padding:6px 0;"><?= e($case['divorce_date']) ?></td></tr>
<tr><td style="padding:6px 0;color:#6B7280;">النوع</td><td style="padding:6px 0;font-weight:600;"><?= e(\App\Modules\Divorce\Models\DivorceCase::getCaseTypeLabel($case['divorce_case_type'])) ?></td></tr>
<tr><td style="padding:6px 0;color:#6B7280;">النسبة</td><td style="padding:6px 0;"><?= e($case['fee_percentage'] ?? '0') ?>%</td></tr>
<tr><td style="padding:6px 0;color:#6B7280;">الرسوم</td><td style="padding:6px 0;font-weight:700;font-size:20px;color:#DC2626;"><?= money($case['fee_amount'] ?? '0') ?></td></tr>
<tr><td style="padding:6px 0;color:#6B7280;">الحالة</td><td style="padding:6px 0;font-weight:700;color:<?= match($case['status']) { 'completed' => '#059669', 'fee_paid' => '#2563EB', default => '#D97706' } ?>;"><?= match($case['status']) { 'completed' => 'مكتمل', 'fee_paid' => 'تم الدفع', default => 'مقدّم' } ?></td></tr>
<?php if ($case['spouse_new_membership_number']): ?><tr><td style="padding:6px 0;color:#6B7280;">رقم العضوية الجديد</td><td style="padding:6px 0;font-weight:700;color:#0D7377;font-size:20px;"><?= e($case['spouse_new_membership_number']) ?></td></tr><?php endif; ?>
</table>
</div>
<!-- Fee Breakdown -->
<div class="card" style="margin-bottom:20px;">
<div style="padding:15px 20px;border-bottom:1px solid #E5E7EB;display:flex;align-items:center;gap:8px;">
<i data-lucide="receipt" style="width:18px;height:18px;color:#D97706;"></i>
<h3 style="margin:0;color:#D97706;font-size:15px;">تفصيل الرسوم</h3>
</div>
<div style="padding:20px;">
<table style="width:100%;max-width:500px;font-size:14px;">
<?php if (!empty($case['membership_value']) && bccomp($case['membership_value'], '0', 2) > 0): ?>
<tr>
<td style="padding:8px 0;color:#6B7280;">قيمة العضوية</td>
<td style="padding:8px 0;font-weight:600;direction:ltr;text-align:left;"><?= money($case['membership_value']) ?></td>
</tr>
<?php endif; ?>
<tr>
<td style="padding:8px 0;color:#6B7280;">نسبة رسوم الطلاق</td>
<td style="padding:8px 0;font-weight:600;"><?= e($case['fee_percentage'] ?? '0') ?>%</td>
</tr>
<tr style="border-top:1px solid #E5E7EB;">
<td style="padding:10px 0;color:#6B7280;">رسوم الطلاق (<?= e($case['fee_percentage'] ?? '0') ?>% من قيمة العضوية)</td>
<td style="padding:10px 0;font-weight:700;direction:ltr;text-align:left;color:#0D7377;"><?= money($percentageFee ?? '0') ?></td>
</tr>
<tr>
<td style="padding:8px 0;color:#6B7280;">رسوم الاستمارة</td>
<td style="padding:8px 0;font-weight:600;direction:ltr;text-align:left;"><?= money($formFee ?? '0') ?></td>
</tr>
<tr style="border-top:2px solid #0D7377;">
<td style="padding:12px 0;font-weight:700;font-size:16px;">الإجمالي المطلوب</td>
<td style="padding:12px 0;font-weight:800;font-size:22px;color:#DC2626;direction:ltr;text-align:left;"><?= money($case['fee_amount'] ?? '0') ?></td>
</tr>
</table>
</div>
</div>
<?php if (!in_array($case['status'], ['completed', 'fee_paid']) && bccomp($case['fee_amount'] ?? '0', '0', 2) > 0): ?>
<div class="card" style="padding:20px;margin-bottom:20px;background:#FFF7ED;border:2px solid #F59E0B;">
<h4 style="margin:0 0 15px;color:#D97706;">💰 دفع رسوم الطلاق</h4>
......@@ -35,4 +67,5 @@
<button type="submit" class="btn btn-primary" onclick="return confirm('⚠ إتمام حالة الطلاق — سيتم إنشاء عضوية جديدة. متأكد؟')">✅ إتمام حالة الطلاق</button>
</form>
<?php endif; ?>
<script>document.addEventListener('DOMContentLoaded', function() { if (typeof lucide !== 'undefined') lucide.createIcons(); });</script>
<?php $__template->endSection(); ?>
\ No newline at end of file
......@@ -15,6 +15,7 @@ use App\Modules\Members\Services\MemberSearchService;
use App\Modules\Members\Services\BillingService;
use App\Modules\Payments\Services\PaymentService;
use App\Modules\Rules\Services\RuleEngine;
use App\Modules\Pricing\Models\SpecialDiscount;
class MemberController extends Controller
{
......@@ -124,6 +125,11 @@ class MemberController extends Controller
$bill = BillingService::getMemberBill((int) $id);
$formFilled = ($member->qualification_id !== null && $member->qualification_id > 0);
$specialDiscount = null;
if ($member->special_discount_id) {
$specialDiscount = $db->selectOne("SELECT * FROM special_discounts WHERE id = ?", [(int) $member->special_discount_id]);
}
return $this->view('Members.Views.show', [
'member' => $member,
'branchName' => $branch['name_ar'] ?? '—',
......@@ -133,6 +139,7 @@ class MemberController extends Controller
'bill' => $bill,
'formFee' => MemberNumberGenerator::getFormFee(),
'formFilled' => $formFilled,
'specialDiscount' => $specialDiscount,
]);
}
......@@ -239,6 +246,7 @@ class MemberController extends Controller
'qualifications' => $db->select("SELECT id, name_ar FROM qualifications WHERE is_active = 1 ORDER BY sort_order"),
'governorates' => $db->select("SELECT code, name_ar FROM governorates WHERE is_active = 1 ORDER BY name_ar"),
'countries' => $db->select("SELECT nationality_ar FROM countries WHERE is_active = 1 ORDER BY name_ar"),
'specialDiscounts' => SpecialDiscount::allActive(),
]);
}
......@@ -260,6 +268,18 @@ class MemberController extends Controller
$pricing = $db->selectOne("SELECT price FROM pricing_configs WHERE branch_id = ? AND qualification_id = ? AND membership_type = 'working' AND is_active = 1 AND effective_from <= CURDATE() AND (effective_to IS NULL OR effective_to >= CURDATE()) ORDER BY effective_from DESC LIMIT 1", [(int) $member->branch_id, (int) $update['qualification_id']]);
if ($pricing) $update['membership_value'] = $pricing['price'];
}
// Special discount
$discountId = trim((string) ($data['special_discount_id'] ?? ''));
if ($discountId !== '' && $discountId !== '0') {
$discountRow = $db->selectOne("SELECT * FROM special_discounts WHERE id = ? AND is_active = 1", [(int) $discountId]);
if ($discountRow) {
$update['special_discount_id'] = (int) $discountId;
$mValue = $update['membership_value'] ?? ($member->membership_value ?? '0.00');
$update['discount_amount'] = bcdiv(bcmul($mValue, $discountRow['discount_percentage'], 4), '100', 2);
}
}
if ($member->status === 'potential') $update['status'] = 'under_review';
if (!empty($update)) $member->update($update);
return $this->redirect('/members/' . $id)->withSuccess('تم ملء الاستمارة');
......@@ -271,17 +291,19 @@ class MemberController extends Controller
$member = Member::find((int) $id);
if (!$member) return $this->redirect('/members')->withError('العضو غير موجود');
return $this->view('Members.Views.edit', [
'member' => $member,
'branches' => $db->select("SELECT id, name_ar FROM branches WHERE is_active = 1"),
'qualifications' => $db->select("SELECT id, name_ar FROM qualifications WHERE is_active = 1 ORDER BY sort_order"),
'governorates' => $db->select("SELECT code, name_ar FROM governorates WHERE is_active = 1"),
'countries' => $db->select("SELECT nationality_ar FROM countries WHERE is_active = 1 ORDER BY name_ar"),
'isSuperAdmin' => self::isSuperAdmin(),
'member' => $member,
'branches' => $db->select("SELECT id, name_ar FROM branches WHERE is_active = 1"),
'qualifications' => $db->select("SELECT id, name_ar FROM qualifications WHERE is_active = 1 ORDER BY sort_order"),
'governorates' => $db->select("SELECT code, name_ar FROM governorates WHERE is_active = 1"),
'countries' => $db->select("SELECT nationality_ar FROM countries WHERE is_active = 1 ORDER BY name_ar"),
'isSuperAdmin' => self::isSuperAdmin(),
'specialDiscounts' => SpecialDiscount::allActive(),
]);
}
public function update(Request $request, string $id): Response
{
$db = App::getInstance()->db();
$member = Member::find((int) $id);
if (!$member) return $this->redirect('/members')->withError('العضو غير موجود');
......@@ -328,7 +350,6 @@ class MemberController extends Controller
$errors = array_merge($errors, $parsed['errors']);
} else {
// Check duplicate
$db = App::getInstance()->db();
$dup = $db->selectOne(
"SELECT id, full_name_ar FROM members WHERE national_id = ? AND is_archived = 0 AND id != ?",
[$newNid, (int) $id]
......@@ -354,6 +375,39 @@ class MemberController extends Controller
}
}
// ── Special Discount handling ──
if (array_key_exists('special_discount_id', $data)) {
$discountId = trim((string) ($data['special_discount_id'] ?? ''));
if ($discountId === '' || $discountId === '0') {
$update['special_discount_id'] = null;
$update['discount_amount'] = null;
} else {
$discountRow = $db->selectOne("SELECT * FROM special_discounts WHERE id = ? AND is_active = 1", [(int) $discountId]);
if ($discountRow) {
$update['special_discount_id'] = (int) $discountId;
$membershipValue = $member->membership_value ?? '0.00';
$update['discount_amount'] = bcdiv(bcmul($membershipValue, $discountRow['discount_percentage'], 4), '100', 2);
// Handle document upload if required
if ($discountRow['requires_document'] && !empty($_FILES['discount_document']['tmp_name'])) {
$file = $_FILES['discount_document'];
$allowedTypes = ['application/pdf', 'image/jpeg', 'image/png'];
$finfo = new \finfo(FILEINFO_MIME_TYPE);
$mimeType = $finfo->file($file['tmp_name']);
if (in_array($mimeType, $allowedTypes) && $file['size'] <= 10485760) {
$ext = pathinfo($file['name'], PATHINFO_EXTENSION);
$storedName = 'discount_' . (int) $id . '_' . date('Ymd_His') . '_' . bin2hex(random_bytes(4)) . '.' . $ext;
$uploadDir = App::getInstance()->basePath() . '/storage/uploads/discounts/';
if (!is_dir($uploadDir)) mkdir($uploadDir, 0755, true);
if (move_uploaded_file($file['tmp_name'], $uploadDir . $storedName)) {
$update['special_discount_document'] = 'storage/uploads/discounts/' . $storedName;
}
}
}
}
}
}
if (!empty($update)) $member->update($update);
return $this->redirect('/members/' . $id)->withSuccess('تم تحديث البيانات');
}
......
......@@ -28,7 +28,8 @@ class Member extends Model
'area', 'governorate', 'correspondence_address',
'employment_type', 'occupation', 'job_title', 'employment_date',
'business_address', 'office_phone', 'office_fax', 'business_activity',
'membership_value', 'payment_method', 'referral_source', 'photo_path',
'membership_value', 'special_discount_id', 'special_discount_document', 'discount_amount',
'payment_method', 'referral_source', 'photo_path',
'workflow_instance_id',
];
......
......@@ -67,6 +67,26 @@ final class BillingService
'category' => 'required',
];
// ── 2b. Special Discount ──
if (!empty($member['special_discount_id'])) {
$discountRow = $db->selectOne(
"SELECT sd.name_ar, sd.discount_percentage FROM special_discounts sd WHERE sd.id = ?",
[(int) $member['special_discount_id']]
);
if ($discountRow) {
$discountAmount = $member['discount_amount'] ?? bcdiv(bcmul($membershipValue, $discountRow['discount_percentage'], 4), '100', 2);
// Discount is a negative line item
$items[] = [
'type' => 'special_discount',
'label' => 'خصم خاص: ' . $discountRow['name_ar'] . ' (' . $discountRow['discount_percentage'] . '%)',
'amount' => '-' . $discountAmount,
'paid' => false,
'included' => false,
'category' => 'discount',
];
}
}
// ── 3. Spouses ──
$spouses = [];
try {
......
<?php $__template->layout('Layout.main'); ?>
<?php $__template->section('title'); ?>تعديل العضو: <?= e($member->full_name_ar) ?><?php $__template->endSection(); ?>
<?php $__template->section('content'); ?>
<form method="POST" action="/members/<?= (int) $member->id ?>">
<form method="POST" action="/members/<?= (int) $member->id ?>" enctype="multipart/form-data">
<?= csrf_field() ?>
<?php if (!empty($isSuperAdmin)): ?>
......@@ -63,7 +63,85 @@
</div>
</div>
<!-- Special Discount -->
<?php if (!empty($specialDiscounts)): ?>
<div class="card" style="margin-bottom:20px;padding:20px;border-right:4px solid #D97706;">
<div style="display:flex;align-items:center;gap:8px;margin-bottom:15px;">
<i data-lucide="percent" style="width:18px;height:18px;color:#D97706;"></i>
<h3 style="margin:0;color:#D97706;font-size:15px;">خصم خاص</h3>
</div>
<div style="display:grid;grid-template-columns:1fr 1fr;gap:15px;">
<div class="form-group">
<label class="form-label">نوع الخصم الخاص</label>
<select name="special_discount_id" id="special_discount_select" class="form-select">
<option value="">— بدون خصم —</option>
<?php foreach ($specialDiscounts as $sd): ?>
<option value="<?= (int) $sd['id'] ?>"
data-pct="<?= e($sd['discount_percentage']) ?>"
data-doc="<?= (int) $sd['requires_document'] ?>"
<?= ((int) ($member->special_discount_id ?? 0)) === (int) $sd['id'] ? 'selected' : '' ?>>
<?= e($sd['name_ar']) ?> (<?= e($sd['discount_percentage']) ?>%)
</option>
<?php endforeach; ?>
</select>
</div>
<div class="form-group" id="discount-info" style="display:<?= $member->special_discount_id ? 'block' : 'none' ?>;">
<label class="form-label">قيمة الخصم</label>
<div id="discount-amount" style="font-size:18px;font-weight:700;color:#059669;padding:8px 0;">
<?php if ($member->discount_amount && bccomp($member->discount_amount, '0', 2) > 0): ?>
<?= money($member->discount_amount) ?>
<?php else: ?>
<?php endif; ?>
</div>
</div>
</div>
<div class="form-group" id="discount-doc-group" style="display:none;margin-top:10px;">
<label class="form-label">مستند إثبات الخصم (PDF, JPG, PNG) <span style="color:#DC2626;">*</span></label>
<input type="file" name="discount_document" id="discount_document" class="form-input" accept=".pdf,.jpg,.jpeg,.png" style="padding:8px;">
<?php if (!empty($member->special_discount_document)): ?>
<small style="color:#059669;font-size:12px;display:block;margin-top:4px;">
<i data-lucide="file-check" style="width:12px;height:12px;vertical-align:middle;"></i>
مستند مرفق بالفعل — <a href="/<?= e($member->special_discount_document) ?>" target="_blank" style="color:#0D7377;">عرض</a>
</small>
<?php endif; ?>
</div>
</div>
<?php endif; ?>
<button type="submit" class="btn btn-primary">حفظ التعديلات</button>
<a href="/members/<?= (int) $member->id ?>" class="btn btn-outline">إلغاء</a>
</form>
<script>
document.addEventListener('DOMContentLoaded', function() {
if (typeof lucide !== 'undefined') lucide.createIcons();
var sel = document.getElementById('special_discount_select');
if (!sel) return;
var infoDiv = document.getElementById('discount-info');
var amountDiv = document.getElementById('discount-amount');
var docGroup = document.getElementById('discount-doc-group');
var membershipValue = <?= json_encode($member->membership_value ?? '0.00') ?>;
function updateDiscount() {
var opt = sel.options[sel.selectedIndex];
if (!opt || !opt.value) {
infoDiv.style.display = 'none';
docGroup.style.display = 'none';
return;
}
var pct = parseFloat(opt.getAttribute('data-pct') || '0');
var needsDoc = parseInt(opt.getAttribute('data-doc') || '0');
var val = parseFloat(membershipValue) || 0;
var discountAmt = (val * pct / 100).toFixed(2);
amountDiv.textContent = discountAmt + ' ج.م';
infoDiv.style.display = 'block';
docGroup.style.display = needsDoc ? 'block' : 'none';
}
sel.addEventListener('change', updateDiscount);
updateDiscount();
});
</script>
<?php $__template->endSection(); ?>
\ No newline at end of file
......@@ -241,6 +241,26 @@
</div>
</div>
<!-- Special Discount -->
<?php if (!empty($specialDiscounts)): ?>
<div class="card" style="margin-bottom:20px;border-right:4px solid #D97706;">
<div style="padding:15px 20px;border-bottom:1px solid #E5E7EB;">
<h3 style="margin:0;color:#D97706;">خصم خاص (اختياري)</h3>
</div>
<div style="padding:20px;display:grid;grid-template-columns:1fr 1fr;gap:15px;">
<div class="form-group">
<label class="form-label">نوع الخصم الخاص</label>
<select name="special_discount_id" class="form-select">
<option value="">— بدون خصم —</option>
<?php foreach ($specialDiscounts as $sd): ?>
<option value="<?= (int) $sd['id'] ?>"><?= e($sd['name_ar']) ?> (<?= e($sd['discount_percentage']) ?>%)</option>
<?php endforeach; ?>
</select>
</div>
</div>
</div>
<?php endif; ?>
<div style="display:flex;gap:10px;">
<button type="submit" class="btn btn-primary" style="padding:12px 30px;font-size:16px;">✓ حفظ الاستمارة</button>
<a href="/members/<?= (int) $member->id ?>" class="btn btn-outline" style="padding:12px 20px;">إلغاء</a>
......
......@@ -241,8 +241,17 @@ $isActive = ($member->status === 'active');
<div class="card">
<div style="padding:15px 20px;border-bottom:1px solid #E5E7EB;"><h3 style="margin:0;color:#0D7377;font-size:15px;">💰 الملخص المالي</h3></div>
<div style="padding:15px 20px;">
<div style="display:grid;grid-template-columns:1fr 1fr;gap:15px;">
<div style="display:grid;grid-template-columns:<?= !empty($specialDiscount) ? '1fr 1fr 1fr' : '1fr 1fr' ?>;gap:15px;">
<div style="background:#F0FDF4;padding:15px;border-radius:8px;text-align:center;"><div style="font-size:20px;font-weight:700;color:#059669;"><?= money($member->membership_value ?? '0') ?></div><div style="color:#6B7280;font-size:11px;">قيمة العضوية</div></div>
<?php if (!empty($specialDiscount)): ?>
<div style="background:#FFF7ED;padding:15px;border-radius:8px;text-align:center;">
<div style="font-size:20px;font-weight:700;color:#D97706;">-<?= money($member->discount_amount ?? '0') ?></div>
<div style="color:#6B7280;font-size:11px;">خصم: <?= e($specialDiscount['name_ar']) ?> (<?= e($specialDiscount['discount_percentage']) ?>%)</div>
<?php if ($specialDiscount['requires_document'] && !empty($member->special_discount_document)): ?>
<a href="/<?= e($member->special_discount_document) ?>" target="_blank" style="font-size:11px;color:#0D7377;">📄 عرض الإثبات</a>
<?php endif; ?>
</div>
<?php endif; ?>
<div style="background:#EFF6FF;padding:15px;border-radius:8px;text-align:center;"><div style="font-size:20px;font-weight:700;color:#0284C7;"><?= money($bill['total_paid']) ?></div><div style="color:#6B7280;font-size:11px;">إجمالي المدفوع</div></div>
</div>
<a href="/payments/member/<?= (int) $member->id ?>" class="btn btn-outline" style="width:100%;text-align:center;margin-top:10px;">📜 سجل المدفوعات</a>
......
<?php
declare(strict_types=1);
namespace App\Modules\Pricing\Controllers;
use App\Core\Controller;
use App\Core\Request;
use App\Core\Response;
use App\Core\App;
use App\Modules\Pricing\Models\SpecialDiscount;
class SpecialDiscountController extends Controller
{
public function index(Request $request): Response
{
$filters = [
'search' => trim((string) $request->get('q', '')),
'is_active' => $request->get('is_active', ''),
];
$page = max(1, (int) $request->get('page', 1));
$result = SpecialDiscount::search($filters, 25, $page);
return $this->view('Pricing.Views.special_discounts.index', [
'rows' => $result['data'],
'pagination' => $result['pagination'],
'filters' => $filters,
]);
}
public function create(Request $request): Response
{
return $this->view('Pricing.Views.special_discounts.form', [
'discount' => null,
]);
}
public function store(Request $request): Response
{
$nameAr = trim((string) $request->post('name_ar', ''));
$nameEn = trim((string) $request->post('name_en', '')) ?: null;
$percentage = trim((string) $request->post('discount_percentage', '0'));
$requiresDocument = (int) $request->post('requires_document', 0);
$description = trim((string) $request->post('description', '')) ?: null;
if ($nameAr === '') {
return $this->redirect('/pricing/special-discounts/create')->withError('اسم الخصم مطلوب');
}
if (bccomp($percentage, '0', 2) <= 0 || bccomp($percentage, '100', 2) > 0) {
return $this->redirect('/pricing/special-discounts/create')->withError('نسبة الخصم يجب أن تكون بين 0.01% و 100%');
}
$employee = App::getInstance()->currentEmployee();
SpecialDiscount::create([
'name_ar' => $nameAr,
'name_en' => $nameEn,
'discount_percentage' => $percentage,
'requires_document' => $requiresDocument,
'description' => $description,
'is_active' => 1,
]);
return $this->redirect('/pricing/special-discounts')->withSuccess('تم إضافة الخصم الخاص بنجاح');
}
public function edit(Request $request, string $id): Response
{
$db = App::getInstance()->db();
$discount = $db->selectOne("SELECT * FROM `special_discounts` WHERE `id` = ?", [(int) $id]);
if (!$discount) {
return $this->redirect('/pricing/special-discounts')->withError('الخصم غير موجود');
}
return $this->view('Pricing.Views.special_discounts.form', [
'discount' => $discount,
]);
}
public function update(Request $request, string $id): Response
{
$db = App::getInstance()->db();
$discount = $db->selectOne("SELECT * FROM `special_discounts` WHERE `id` = ?", [(int) $id]);
if (!$discount) {
return $this->redirect('/pricing/special-discounts')->withError('الخصم غير موجود');
}
$nameAr = trim((string) $request->post('name_ar', ''));
$nameEn = trim((string) $request->post('name_en', '')) ?: null;
$percentage = trim((string) $request->post('discount_percentage', '0'));
$requiresDocument = (int) $request->post('requires_document', 0);
$description = trim((string) $request->post('description', '')) ?: null;
$isActive = (int) $request->post('is_active', 1);
if ($nameAr === '') {
return $this->redirect("/pricing/special-discounts/{$id}/edit")->withError('اسم الخصم مطلوب');
}
if (bccomp($percentage, '0', 2) <= 0 || bccomp($percentage, '100', 2) > 0) {
return $this->redirect("/pricing/special-discounts/{$id}/edit")->withError('نسبة الخصم يجب أن تكون بين 0.01% و 100%');
}
$db->update('special_discounts', [
'name_ar' => $nameAr,
'name_en' => $nameEn,
'discount_percentage' => $percentage,
'requires_document' => $requiresDocument,
'description' => $description,
'is_active' => $isActive,
'updated_at' => date('Y-m-d H:i:s'),
], '`id` = ?', [(int) $id]);
return $this->redirect('/pricing/special-discounts')->withSuccess('تم تحديث الخصم الخاص');
}
public function toggleActive(Request $request, string $id): Response
{
$db = App::getInstance()->db();
$discount = $db->selectOne("SELECT * FROM `special_discounts` WHERE `id` = ?", [(int) $id]);
if (!$discount) {
return $this->redirect('/pricing/special-discounts')->withError('الخصم غير موجود');
}
$newActive = $discount['is_active'] ? 0 : 1;
$db->update('special_discounts', [
'is_active' => $newActive,
'updated_at' => date('Y-m-d H:i:s'),
], '`id` = ?', [(int) $id]);
return $this->redirect('/pricing/special-discounts')->withSuccess($newActive ? 'تم تفعيل الخصم' : 'تم تعطيل الخصم');
}
}
<?php
declare(strict_types=1);
namespace App\Modules\Pricing\Models;
use App\Core\Model;
use App\Core\App;
class SpecialDiscount extends Model
{
protected static string $table = 'special_discounts';
protected static bool $softDelete = false;
protected static bool $timestamps = true;
protected static array $fillable = [
'name_ar', 'name_en', 'discount_percentage', 'requires_document',
'description', 'is_active',
];
public static function allActive(): array
{
$db = App::getInstance()->db();
return $db->select(
"SELECT * FROM `special_discounts` WHERE `is_active` = 1 ORDER BY `name_ar`"
);
}
public static function search(array $filters, int $perPage = 25, int $page = 1): array
{
$db = App::getInstance()->db();
$where = '1=1';
$params = [];
if (!empty($filters['search'])) {
$where .= ' AND (`name_ar` LIKE ? OR `name_en` LIKE ?)';
$s = '%' . $filters['search'] . '%';
$params[] = $s;
$params[] = $s;
}
if ($filters['is_active'] ?? '' !== '') {
$where .= ' AND `is_active` = ?';
$params[] = (int) $filters['is_active'];
}
$countRow = $db->selectOne("SELECT COUNT(*) as cnt FROM `special_discounts` WHERE {$where}", $params);
$total = (int) ($countRow['cnt'] ?? 0);
$offset = ($page - 1) * $perPage;
$data = $db->select(
"SELECT * FROM `special_discounts` WHERE {$where} ORDER BY `name_ar` LIMIT {$perPage} OFFSET {$offset}",
$params
);
return [
'data' => $data,
'pagination' => \App\Core\Pagination::paginate($total, $perPage, $page),
];
}
}
......@@ -5,4 +5,12 @@ return [
['GET', '/pricing', 'Pricing\Controllers\PricingController@index', ['auth'], 'pricing.view'],
['GET', '/pricing/{id:\d+}/edit', 'Pricing\Controllers\PricingController@edit', ['auth'], 'pricing.edit'],
['POST', '/pricing/{id:\d+}', 'Pricing\Controllers\PricingController@update', ['auth', 'csrf'], 'pricing.edit'],
// Special Discounts
['GET', '/pricing/special-discounts', 'Pricing\Controllers\SpecialDiscountController@index', ['auth'], 'pricing.special_discounts.view'],
['GET', '/pricing/special-discounts/create', 'Pricing\Controllers\SpecialDiscountController@create', ['auth'], 'pricing.special_discounts.create'],
['POST', '/pricing/special-discounts', 'Pricing\Controllers\SpecialDiscountController@store', ['auth', 'csrf'], 'pricing.special_discounts.create'],
['GET', '/pricing/special-discounts/{id:\d+}/edit', 'Pricing\Controllers\SpecialDiscountController@edit', ['auth'], 'pricing.special_discounts.edit'],
['POST', '/pricing/special-discounts/{id:\d+}', 'Pricing\Controllers\SpecialDiscountController@update', ['auth', 'csrf'], 'pricing.special_discounts.edit'],
['POST', '/pricing/special-discounts/{id:\d+}/toggle', 'Pricing\Controllers\SpecialDiscountController@toggleActive', ['auth', 'csrf'], 'pricing.special_discounts.edit'],
];
\ No newline at end of file
<?php $__template->layout('Layout.main'); ?>
<?php $__template->section('title'); ?><?= $discount ? 'تعديل خصم: ' . e($discount['name_ar']) : 'إضافة خصم خاص جديد' ?><?php $__template->endSection(); ?>
<?php $__template->section('page_actions'); ?>
<a href="/pricing/special-discounts" class="btn btn-outline">
<i data-lucide="arrow-right" style="width:15px;height:15px;vertical-align:middle;margin-left:4px;"></i> العودة للقائمة
</a>
<?php $__template->endSection(); ?>
<?php $__template->section('content'); ?>
<form method="POST" action="<?= $discount ? '/pricing/special-discounts/' . (int) $discount['id'] : '/pricing/special-discounts' ?>">
<?= csrf_field() ?>
<div class="card" style="padding:20px;margin-bottom:20px;">
<div style="display:flex;align-items:center;gap:8px;margin-bottom:20px;">
<i data-lucide="percent" style="width:18px;height:18px;color:#0D7377;"></i>
<h3 style="margin:0;color:#0D7377;font-size:15px;">بيانات الخصم الخاص</h3>
</div>
<div style="display:grid;grid-template-columns:1fr 1fr;gap:15px;">
<div class="form-group">
<label class="form-label">اسم الخصم بالعربي <span style="color:#DC2626;">*</span></label>
<input type="text" name="name_ar" value="<?= e($discount['name_ar'] ?? '') ?>" class="form-input" required maxlength="200" placeholder="مثال: عضو شباب ورياضة">
</div>
<div class="form-group">
<label class="form-label">اسم الخصم بالإنجليزي</label>
<input type="text" name="name_en" value="<?= e($discount['name_en'] ?? '') ?>" class="form-input" maxlength="200" placeholder="e.g. Youth & Sports Member">
</div>
<div class="form-group">
<label class="form-label">نسبة الخصم (%) <span style="color:#DC2626;">*</span></label>
<input type="number" name="discount_percentage" value="<?= e($discount['discount_percentage'] ?? '') ?>" class="form-input" required min="0.01" max="100" step="0.01" style="direction:ltr;text-align:left;" placeholder="مثال: 15.00">
<small style="color:#6B7280;font-size:11px;">نسبة الخصم من قيمة العضوية</small>
</div>
<div class="form-group">
<label class="form-label">يحتاج إثبات مستند؟</label>
<select name="requires_document" class="form-select">
<option value="0" <?= empty($discount['requires_document']) ? 'selected' : '' ?>>لا — بدون إثبات</option>
<option value="1" <?= !empty($discount['requires_document']) ? 'selected' : '' ?>>نعم — يجب رفع مستند</option>
</select>
<small style="color:#6B7280;font-size:11px;">إذا اختير "نعم"، سيُطلب رفع مستند إثبات عند تطبيق الخصم على العضو</small>
</div>
<?php if ($discount): ?>
<div class="form-group">
<label class="form-label">الحالة</label>
<select name="is_active" class="form-select">
<option value="1" <?= $discount['is_active'] ? 'selected' : '' ?>>فعال</option>
<option value="0" <?= !$discount['is_active'] ? 'selected' : '' ?>>معطل</option>
</select>
</div>
<?php endif; ?>
<div class="form-group" style="grid-column:1/-1;">
<label class="form-label">وصف / ملاحظات</label>
<textarea name="description" class="form-textarea" rows="3" placeholder="وصف اختياري لنوع الخصم..."><?= e($discount['description'] ?? '') ?></textarea>
</div>
</div>
</div>
<button type="submit" class="btn btn-primary">
<i data-lucide="save" style="width:15px;height:15px;vertical-align:middle;margin-left:4px;"></i>
<?= $discount ? 'حفظ التعديلات' : 'إضافة الخصم' ?>
</button>
<a href="/pricing/special-discounts" class="btn btn-outline">إلغاء</a>
</form>
<script>document.addEventListener('DOMContentLoaded', function() { if (typeof lucide !== 'undefined') lucide.createIcons(); });</script>
<?php $__template->endSection(); ?>
<?php $__template->layout('Layout.main'); ?>
<?php $__template->section('title'); ?>الخصومات الخاصة<?php $__template->endSection(); ?>
<?php $__template->section('page_actions'); ?>
<a href="/pricing/special-discounts/create" class="btn btn-primary">
<i data-lucide="plus" style="width:15px;height:15px;vertical-align:middle;margin-left:4px;"></i> إضافة خصم جديد
</a>
<a href="/pricing" class="btn btn-outline">
<i data-lucide="arrow-right" style="width:15px;height:15px;vertical-align:middle;margin-left:4px;"></i> العودة للتسعير
</a>
<?php $__template->endSection(); ?>
<?php $__template->section('content'); ?>
<!-- Search -->
<div class="card" style="margin-bottom:15px;padding:15px;">
<form method="GET" action="/pricing/special-discounts" style="display:flex;gap:10px;align-items:end;">
<div class="form-group" style="flex:1;">
<label class="form-label">بحث</label>
<input type="text" name="q" value="<?= e($filters['search'] ?? '') ?>" class="form-input" placeholder="اسم الخصم...">
</div>
<button type="submit" class="btn btn-primary" style="padding:8px 20px;">
<i data-lucide="search" style="width:14px;height:14px;vertical-align:middle;"></i> بحث
</button>
</form>
</div>
<!-- Table -->
<div class="card">
<div style="padding:15px 20px;border-bottom:1px solid #E5E7EB;display:flex;align-items:center;gap:8px;">
<i data-lucide="percent" style="width:18px;height:18px;color:#0D7377;"></i>
<h3 style="margin:0;color:#0D7377;font-size:15px;">قائمة الخصومات الخاصة</h3>
</div>
<?php if (!empty($rows)): ?>
<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 foreach ($rows as $row): ?>
<tr>
<td><?= (int) $row['id'] ?></td>
<td style="font-weight:600;"><?= e($row['name_ar']) ?></td>
<td style="font-weight:700;color:#0D7377;"><?= e($row['discount_percentage']) ?>%</td>
<td>
<?php if ($row['requires_document']): ?>
<span style="display:inline-block;padding:3px 10px;border-radius:10px;font-size:12px;font-weight:600;background:#FFF7ED;color:#D97706;">
<i data-lucide="file-check" style="width:12px;height:12px;vertical-align:middle;margin-left:3px;"></i> مطلوب
</span>
<?php else: ?>
<span style="color:#9CA3AF;font-size:12px;">غير مطلوب</span>
<?php endif; ?>
</td>
<td>
<?php if ($row['is_active']): ?>
<span style="display:inline-block;padding:3px 10px;border-radius:10px;font-size:12px;font-weight:600;background:#ECFDF5;color:#059669;">فعال</span>
<?php else: ?>
<span style="display:inline-block;padding:3px 10px;border-radius:10px;font-size:12px;font-weight:600;background:#F3F4F6;color:#6B7280;">معطل</span>
<?php endif; ?>
</td>
<td style="font-size:12px;color:#6B7280;max-width:200px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;"><?= e($row['description'] ?? '—') ?></td>
<td style="white-space:nowrap;">
<a href="/pricing/special-discounts/<?= (int) $row['id'] ?>/edit" class="btn btn-sm btn-outline" style="font-size:12px;padding:4px 10px;">
<i data-lucide="edit" style="width:12px;height:12px;vertical-align:middle;"></i> تعديل
</a>
<form method="POST" action="/pricing/special-discounts/<?= (int) $row['id'] ?>/toggle" style="display:inline;">
<?= csrf_field() ?>
<button type="submit" class="btn btn-sm btn-outline" style="font-size:12px;padding:4px 10px;<?= $row['is_active'] ? 'color:#DC2626;' : 'color:#059669;' ?>">
<?= $row['is_active'] ? 'تعطيل' : 'تفعيل' ?>
</button>
</form>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<?php if (!empty($pagination) && $pagination['last_page'] > 1): ?>
<div style="padding:15px 20px;border-top:1px solid #E5E7EB;text-align:center;">
<?php for ($i = 1; $i <= $pagination['last_page']; $i++): ?>
<?php if ($i == $pagination['current_page']): ?>
<span style="display:inline-block;padding:4px 12px;background:#0D7377;color:#fff;border-radius:4px;font-size:13px;margin:0 2px;"><?= $i ?></span>
<?php else: ?>
<a href="?page=<?= $i ?>&q=<?= urlencode($filters['search'] ?? '') ?>" style="display:inline-block;padding:4px 12px;color:#0D7377;text-decoration:none;font-size:13px;margin:0 2px;"><?= $i ?></a>
<?php endif; ?>
<?php endfor; ?>
</div>
<?php endif; ?>
<?php else: ?>
<div style="padding:60px 20px;text-align:center;">
<div style="margin-bottom:15px;"><i data-lucide="percent" style="width:48px;height:48px;color:#D1D5DB;"></i></div>
<h3 style="color:#6B7280;margin:0 0 8px;">لا توجد خصومات خاصة</h3>
<p style="color:#9CA3AF;font-size:14px;margin:0 0 15px;">أضف أنواع الخصومات الخاصة (مثل: عضو شباب ورياضة) لتطبيقها على الأعضاء.</p>
<a href="/pricing/special-discounts/create" class="btn btn-primary">إضافة خصم جديد</a>
</div>
<?php endif; ?>
</div>
<script>document.addEventListener('DOMContentLoaded', function() { if (typeof lucide !== 'undefined') lucide.createIcons(); });</script>
<?php $__template->endSection(); ?>
......@@ -12,9 +12,10 @@ MenuRegistry::register('rules_pricing', [
'permission' => 'rules.view',
'order' => 150,
'children' => [
['label_ar' => 'محرك القواعد', 'label_en' => 'Rules Engine', 'route' => '/rules', 'permission' => 'rules.view', 'order' => 1],
['label_ar' => 'التسعير', 'label_en' => 'Pricing', 'route' => '/pricing', 'permission' => 'pricing.view', 'order' => 2],
['label_ar' => 'كتالوج الخدمات', 'label_en' => 'Service Catalog', 'route' => '/catalog', 'permission' => 'pricing.view', 'order' => 3],
['label_ar' => 'محرك القواعد', 'label_en' => 'Rules Engine', 'route' => '/rules', 'permission' => 'rules.view', 'order' => 1],
['label_ar' => 'التسعير', 'label_en' => 'Pricing', 'route' => '/pricing', 'permission' => 'pricing.view', 'order' => 2],
['label_ar' => 'كتالوج الخدمات', 'label_en' => 'Service Catalog', 'route' => '/catalog', 'permission' => 'pricing.view', 'order' => 3],
['label_ar' => 'خصومات خاصة', 'label_en' => 'Special Discounts', 'route' => '/pricing/special-discounts', 'permission' => 'pricing.special_discounts.view', 'order' => 4],
],
]);
......@@ -26,7 +27,10 @@ PermissionRegistry::register('rules', [
]);
PermissionRegistry::register('pricing', [
'pricing.view' => ['ar' => 'عرض التسعير', 'en' => 'View Pricing'],
'pricing.edit' => ['ar' => 'تعديل التسعير', 'en' => 'Edit Pricing'],
'pricing.create' => ['ar' => 'إنشاء تسعير', 'en' => 'Create Pricing'],
'pricing.view' => ['ar' => 'عرض التسعير', 'en' => 'View Pricing'],
'pricing.edit' => ['ar' => 'تعديل التسعير', 'en' => 'Edit Pricing'],
'pricing.create' => ['ar' => 'إنشاء تسعير', 'en' => 'Create Pricing'],
'pricing.special_discounts.view' => ['ar' => 'عرض الخصومات الخاصة', 'en' => 'View Special Discounts'],
'pricing.special_discounts.create'=> ['ar' => 'إنشاء خصم خاص', 'en' => 'Create Special Discount'],
'pricing.special_discounts.edit' => ['ar' => 'تعديل خصم خاص', 'en' => 'Edit Special Discount'],
]);
\ No newline at end of file
......@@ -69,10 +69,25 @@ class TransferController extends Controller
return $this->redirect("/transfers/create/{$memberId}")->withError('يجب اختيار الابن/الابنة');
}
// Age validation: dependents under 25 cannot self-separate
if ($transferType === 'child_separation' && $childId) {
$child = $db->selectOne("SELECT * FROM children WHERE id = ? AND is_archived = 0", [$childId]);
if ($child && !empty($child['date_of_birth'])) {
$dob = new \DateTime($child['date_of_birth']);
$now = new \DateTime();
$age = (int) $now->diff($dob)->y;
if ($age < 25) {
return $this->redirect("/transfers/create/{$memberId}")->withError(
'لا يمكن فصل الملحق تحت سن 25 سنة. العمر الحالي: ' . $age . ' سنة. يتم الفصل الوجوبي عند بلوغ 25 سنة.'
);
}
}
}
// Calculate fees
$qualCode = null;
if ($childId) {
$child = $db->selectOne("SELECT * FROM children WHERE id = ?", [$childId]);
$child = $child ?? $db->selectOne("SELECT * FROM children WHERE id = ?", [$childId]);
// Use child's qualification if available, otherwise parent's
}
......
......@@ -20,12 +20,21 @@
</div>
<div class="form-group" id="child-select" style="display:none;">
<label class="form-label">اختيار الابن/الابنة</label>
<select name="child_id" class="form-select">
<select name="child_id" id="child_id" class="form-select">
<option value="">-- اختر --</option>
<?php foreach ($children as $c): ?>
<option value="<?= (int) $c['id'] ?>"><?= e($c['full_name_ar']) ?><?= (int) ($c['age_years'] ?? 0) ?> سنة (<?= $c['gender'] === 'male' ? 'ذكر' : 'أنثى' ?>)</option>
<?php foreach ($children as $c):
$dob = $c['date_of_birth'] ?? null;
$childAge = 0;
if ($dob) {
$childAge = (int) (new \DateTime($dob))->diff(new \DateTime())->y;
}
?>
<option value="<?= (int) $c['id'] ?>" data-age="<?= $childAge ?>"><?= e($c['full_name_ar']) ?><?= $childAge ?> سنة (<?= $c['gender'] === 'male' ? 'ذكر' : 'أنثى' ?>)</option>
<?php endforeach; ?>
</select>
<div id="age-warning" style="display:none;margin-top:6px;padding:8px 12px;background:#FEE2E2;border:1px solid #FECACA;border-radius:6px;font-size:13px;color:#DC2626;">
<strong>تنبيه:</strong> لا يمكن فصل الملحقين تحت سن 25 سنة. يتم الفصل الوجوبي فقط عند بلوغ 25 سنة.
</div>
</div>
<div class="form-group" style="grid-column:1/-1;">
<label class="form-label">ملاحظات</label>
......@@ -62,33 +71,74 @@
<a href="/members/<?= (int) $member['id'] ?>" class="btn btn-outline">إلغاء</a>
</form>
<script>
document.getElementById('transfer_type').addEventListener('change', function() {
var cs = document.getElementById('child-select');
cs.style.display = (this.value === 'child_separation' || this.value === 'child_mandatory_25') ? 'block' : 'none';
(function() {
var transferType = document.getElementById('transfer_type');
var childSelect = document.getElementById('child_id');
var childGroup = document.getElementById('child-select');
var ageWarning = document.getElementById('age-warning');
var allOptions = Array.from(childSelect.querySelectorAll('option[data-age]'));
// Fetch fee preview
if (this.value) {
var memberId = <?= (int) $member['id'] ?>;
var childId = document.querySelector('[name=child_id]').value || '';
fetch('/api/transfers/calculate-fee', {
method: 'POST',
headers: {'Content-Type': 'application/x-www-form-urlencoded', 'X-CSRF-TOKEN': document.querySelector('[name=_csrf_token]').value},
body: 'member_id=' + memberId + '&child_id=' + childId
})
.then(function(r) { return r.json(); })
.then(function(d) {
if (d.success) {
document.getElementById('fp-separation').textContent = d.separation_fee;
document.getElementById('fp-form').textContent = d.form_fee;
document.getElementById('fp-annual').textContent = d.annual_subscription_fee;
document.getElementById('fp-total').textContent = d.total_fee;
document.getElementById('fee-preview').style.display = 'block';
function filterChildren() {
var type = transferType.value;
var isChildType = (type === 'child_separation' || type === 'child_mandatory_25');
childGroup.style.display = isChildType ? 'block' : 'none';
// Reset selection
childSelect.value = '';
ageWarning.style.display = 'none';
allOptions.forEach(function(opt) {
var age = parseInt(opt.getAttribute('data-age'), 10);
if (type === 'child_separation') {
// Only show children 25 and above for voluntary separation
opt.style.display = age >= 25 ? '' : 'none';
opt.disabled = age < 25;
} else {
// Show all children for other types
opt.style.display = '';
opt.disabled = false;
}
});
// Show warning if child_separation and no eligible children
if (type === 'child_separation') {
var hasEligible = allOptions.some(function(opt) { return parseInt(opt.getAttribute('data-age'), 10) >= 25; });
if (!hasEligible) {
ageWarning.style.display = 'block';
}
})
.catch(function() {});
} else {
document.getElementById('fee-preview').style.display = 'none';
}
fetchFeePreview();
}
function fetchFeePreview() {
var type = transferType.value;
if (type) {
var memberId = <?= (int) $member['id'] ?>;
var childId = childSelect.value || '';
fetch('/api/transfers/calculate-fee', {
method: 'POST',
headers: {'Content-Type': 'application/x-www-form-urlencoded', 'X-CSRF-TOKEN': document.querySelector('[name=_csrf_token]').value},
body: 'member_id=' + memberId + '&child_id=' + childId
})
.then(function(r) { return r.json(); })
.then(function(d) {
if (d.success) {
document.getElementById('fp-separation').textContent = d.separation_fee;
document.getElementById('fp-form').textContent = d.form_fee;
document.getElementById('fp-annual').textContent = d.annual_subscription_fee;
document.getElementById('fp-total').textContent = d.total_fee;
document.getElementById('fee-preview').style.display = 'block';
}
})
.catch(function() {});
} else {
document.getElementById('fee-preview').style.display = 'none';
}
}
});
transferType.addEventListener('change', filterChildren);
childSelect.addEventListener('change', fetchFeePreview);
})();
</script>
<?php $__template->endSection(); ?>
\ No newline at end of file
<?php $__template->layout('Layout.main'); ?>
<?php $__template->section('title'); ?>طلب تحويل #<?= (int) $transfer['id'] ?><?php $__template->endSection(); ?>
<?php $__template->section('content'); ?>
<?php
$typeLabels = ['child_separation' => 'فصل أبناء', 'child_mandatory_25' => 'تحويل وجوبي (25 سنة)', 'sports_conversion' => 'تحويل رياضي لعامل', 'cross_branch' => 'تحويل بين فروع'];
?>
<div class="card" style="padding:20px;margin-bottom:20px;">
<table style="width:100%;max-width:700px;font-size:14px;">
<tr><td style="padding:8px 0;color:#6B7280;width:35%;">العضو المصدر</td><td style="padding:8px 0;"><a href="/members/<?= (int) $transfer['source_member_id'] ?>" style="color:#0D7377;font-weight:600;"><?= e($transfer['source_name'] ?? '') ?></a></td></tr>
<tr><td style="padding:8px 0;color:#6B7280;">النوع</td><td style="padding:8px 0;font-weight:600;"><?= e($transfer['transfer_type']) ?></td></tr>
<tr><td style="padding:8px 0;color:#6B7280;">رسوم الفصل</td><td style="padding:8px 0;font-weight:700;font-size:20px;color:#0D7377;"><?= money($transfer['separation_fee'] ?? '0') ?></td></tr>
<tr><td style="padding:8px 0;color:#6B7280;">رسوم الاستمارة</td><td style="padding:8px 0;"><?= money($transfer['form_fee'] ?? '0') ?></td></tr>
<tr><td style="padding:8px 0;color:#6B7280;">الإجمالي</td><td style="padding:8px 0;font-weight:700;font-size:22px;color:#DC2626;"><?= money($transfer['total_fee'] ?? '0') ?></td></tr>
<?php if (!empty($transfer['child_name'])): ?>
<tr><td style="padding:8px 0;color:#6B7280;">الابن/الابنة</td><td style="padding:8px 0;font-weight:600;"><?= e($transfer['child_name']) ?></td></tr>
<?php endif; ?>
<?php if (!empty($transfer['spouse_name'])): ?>
<tr><td style="padding:8px 0;color:#6B7280;">الزوج/ة</td><td style="padding:8px 0;font-weight:600;"><?= e($transfer['spouse_name']) ?></td></tr>
<?php endif; ?>
<tr><td style="padding:8px 0;color:#6B7280;">النوع</td><td style="padding:8px 0;font-weight:600;"><?= e($typeLabels[$transfer['transfer_type']] ?? $transfer['transfer_type']) ?></td></tr>
<tr><td style="padding:8px 0;color:#6B7280;">الحالة</td><td style="padding:8px 0;font-weight:700;color:<?= match($transfer['status']) { 'completed' => '#059669', 'fee_paid' => '#2563EB', 'approved' => '#0284C7', 'rejected' => '#DC2626', default => '#D97706' } ?>;"><?= match($transfer['status']) { 'completed' => 'مكتمل', 'fee_paid' => 'تم الدفع', 'approved' => 'معتمد', 'rejected' => 'مرفوض', default => 'مقدّم' } ?></td></tr>
<?php if ($transfer['new_membership_number']): ?><tr><td style="padding:8px 0;color:#6B7280;">الرقم الجديد</td><td style="padding:8px 0;font-weight:700;color:#0D7377;font-size:20px;"><?= e($transfer['new_membership_number']) ?></td></tr><?php endif; ?>
</table>
</div>
<!-- Fee Breakdown -->
<div class="card" style="margin-bottom:20px;">
<div style="padding:15px 20px;border-bottom:1px solid #E5E7EB;display:flex;align-items:center;gap:8px;">
<i data-lucide="receipt" style="width:18px;height:18px;color:#D97706;"></i>
<h3 style="margin:0;color:#D97706;font-size:15px;">تفصيل الرسوم</h3>
</div>
<div style="padding:20px;">
<table style="width:100%;max-width:500px;font-size:14px;">
<?php if (!empty($transfer['new_membership_value']) && bccomp($transfer['new_membership_value'], '0', 2) > 0): ?>
<tr>
<td style="padding:8px 0;color:#6B7280;">قيمة العضوية الجديدة</td>
<td style="padding:8px 0;font-weight:600;direction:ltr;text-align:left;"><?= money($transfer['new_membership_value']) ?></td>
</tr>
<?php endif; ?>
<?php if (!empty($transfer['years_since_acquisition'])): ?>
<tr>
<td style="padding:8px 0;color:#6B7280;">سنوات منذ الاكتساب</td>
<td style="padding:8px 0;font-weight:600;"><?= (int) $transfer['years_since_acquisition'] ?> سنة</td>
</tr>
<?php endif; ?>
<?php if (!empty($transfer['fee_percentage'])): ?>
<tr>
<td style="padding:8px 0;color:#6B7280;">نسبة رسوم الفصل</td>
<td style="padding:8px 0;font-weight:600;"><?= e($transfer['fee_percentage']) ?>%</td>
</tr>
<?php endif; ?>
<tr style="border-top:1px solid #E5E7EB;">
<td style="padding:10px 0;color:#6B7280;">رسوم الفصل (<?= e($transfer['fee_percentage'] ?? '0') ?>% من قيمة العضوية)</td>
<td style="padding:10px 0;font-weight:700;direction:ltr;text-align:left;color:#0D7377;"><?= money($transfer['separation_fee'] ?? '0') ?></td>
</tr>
<tr>
<td style="padding:8px 0;color:#6B7280;">رسوم الاستمارة</td>
<td style="padding:8px 0;font-weight:600;direction:ltr;text-align:left;"><?= money($transfer['form_fee'] ?? '0') ?></td>
</tr>
<tr>
<td style="padding:8px 0;color:#6B7280;">اشتراك سنوي + تنمية</td>
<td style="padding:8px 0;font-weight:600;direction:ltr;text-align:left;"><?= money($transfer['annual_subscription_fee'] ?? '0') ?></td>
</tr>
<tr style="border-top:2px solid #0D7377;">
<td style="padding:12px 0;font-weight:700;font-size:16px;">الإجمالي المطلوب</td>
<td style="padding:12px 0;font-weight:800;font-size:22px;color:#DC2626;direction:ltr;text-align:left;"><?= money($transfer['total_fee'] ?? '0') ?></td>
</tr>
</table>
</div>
</div>
<?php if (in_array($transfer['status'], ['requested', 'approved']) && bccomp($transfer['total_fee'] ?? '0', '0', 2) > 0): ?>
<div class="card" style="padding:20px;margin-bottom:20px;background:#FFF7ED;border:2px solid #F59E0B;">
<h4 style="margin:0 0 15px;color:#D97706;">💰 دفع رسوم التحويل/الفصل</h4>
......@@ -34,4 +86,5 @@
<button type="submit" class="btn btn-primary" onclick="return confirm('إتمام التحويل؟')">✅ إتمام التحويل</button>
</form>
<?php endif; ?>
<script>document.addEventListener('DOMContentLoaded', function() { if (typeof lucide !== 'undefined') lucide.createIcons(); });</script>
<?php $__template->endSection(); ?>
\ No newline at end of file
......@@ -5,14 +5,35 @@
<table style="width:100%;max-width:600px;font-size:14px;">
<tr><td style="padding:6px 0;color:#6B7280;width:35%;">المتنازل</td><td style="padding:6px 0;"><a href="/members/<?= (int) $waiver['source_member_id'] ?>" style="color:#0D7377;font-weight:600;"><?= e($waiver['source_name'] ?? '') ?></a></td></tr>
<tr><td style="padding:6px 0;color:#6B7280;">رقم العضوية</td><td style="padding:6px 0;font-weight:700;"><?= e($waiver['membership_number'] ?? '—') ?></td></tr>
<tr><td style="padding:6px 0;color:#6B7280;">قيمة العضوية وقت التنازل</td><td style="padding:6px 0;"><?= money($waiver['membership_value_at_waiver'] ?? '0') ?></td></tr>
<tr><td style="padding:6px 0;color:#6B7280;">نسبة التنازل</td><td style="padding:6px 0;"><?= e($waiver['waiver_fee_percentage'] ?? '30') ?>%</td></tr>
<tr><td style="padding:6px 0;color:#6B7280;">رسوم التنازل</td><td style="padding:6px 0;font-weight:700;font-size:22px;color:#DC2626;"><?= money($waiver['waiver_fee_amount'] ?? '0') ?></td></tr>
<tr><td style="padding:6px 0;color:#6B7280;">الحالة</td><td style="padding:6px 0;font-weight:700;color:<?= match($waiver['status']) { 'completed' => '#059669', 'fee_paid' => '#2563EB', 'approved' => '#0284C7', 'rejected' => '#DC2626', default => '#D97706' } ?>;"><?= match($waiver['status']) { 'requested' => 'مقدم', 'approved' => 'معتمد — في انتظار الدفع', 'fee_paid' => 'تم الدفع', 'completed' => 'مكتمل', 'rejected' => 'مرفوض', default => $waiver['status'] } ?></td></tr>
<?php if ($waiver['target_member_id']): ?><tr><td style="padding:6px 0;color:#6B7280;">المتنازل إليه</td><td style="padding:6px 0;"><a href="/members/<?= (int) $waiver['target_member_id'] ?>" style="color:#0D7377;font-weight:600;">عضو #<?= (int) $waiver['target_member_id'] ?></a></td></tr><?php endif; ?>
</table>
</div>
<!-- Fee Breakdown -->
<div class="card" style="margin-bottom:20px;">
<div style="padding:15px 20px;border-bottom:1px solid #E5E7EB;display:flex;align-items:center;gap:8px;">
<i data-lucide="receipt" style="width:18px;height:18px;color:#D97706;"></i>
<h3 style="margin:0;color:#D97706;font-size:15px;">تفصيل الرسوم</h3>
</div>
<div style="padding:20px;">
<table style="width:100%;max-width:500px;font-size:14px;">
<tr>
<td style="padding:8px 0;color:#6B7280;">قيمة العضوية وقت التنازل</td>
<td style="padding:8px 0;font-weight:600;direction:ltr;text-align:left;"><?= money($waiver['membership_value_at_waiver'] ?? '0') ?></td>
</tr>
<tr>
<td style="padding:8px 0;color:#6B7280;">نسبة التنازل</td>
<td style="padding:8px 0;font-weight:600;"><?= e($waiver['waiver_fee_percentage'] ?? '30') ?>%</td>
</tr>
<tr style="border-top:2px solid #0D7377;">
<td style="padding:12px 0;font-weight:700;font-size:16px;">رسوم التنازل (<?= e($waiver['waiver_fee_percentage'] ?? '30') ?>% من قيمة العضوية)</td>
<td style="padding:12px 0;font-weight:800;font-size:22px;color:#DC2626;direction:ltr;text-align:left;"><?= money($waiver['waiver_fee_amount'] ?? '0') ?></td>
</tr>
</table>
</div>
</div>
<?php if (in_array($waiver['status'], ['requested', 'approved']) && bccomp($waiver['waiver_fee_amount'] ?? '0', '0', 2) > 0): ?>
<div class="card" style="padding:20px;margin-bottom:20px;background:#FFF7ED;border:2px solid #F59E0B;">
<h4 style="margin:0 0 15px;color:#D97706;">💰 دفع رسوم التنازل (30% من قيمة العضوية)</h4>
......@@ -41,4 +62,5 @@
<form method="POST" action="/waivers/<?= (int) $waiver['id'] ?>/reject"><?= csrf_field() ?><button type="submit" class="btn btn-outline" style="color:#DC2626;" onclick="return confirm('رفض التنازل؟')">❌ رفض</button></form>
</div>
<?php endif; ?>
<script>document.addEventListener('DOMContentLoaded', function() { if (typeof lucide !== 'undefined') lucide.createIcons(); });</script>
<?php $__template->endSection(); ?>
\ No newline at end of file
<?php
declare(strict_types=1);
return [
'up' => "
CREATE TABLE IF NOT EXISTS `special_discounts` (
`id` BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
`name_ar` VARCHAR(200) NOT NULL,
`name_en` VARCHAR(200) NULL,
`discount_percentage` DECIMAL(5,2) NOT NULL DEFAULT 0.00,
`requires_document` TINYINT(1) NOT NULL DEFAULT 0,
`description` TEXT NULL,
`is_active` TINYINT(1) NOT NULL DEFAULT 1,
`created_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
`updated_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
`created_by` BIGINT UNSIGNED NULL,
`updated_by` BIGINT UNSIGNED NULL,
INDEX `idx_special_discounts_active` (`is_active`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
",
'down' => "DROP TABLE IF EXISTS `special_discounts`",
];
<?php
declare(strict_types=1);
return [
'up' => "
ALTER TABLE `members`
ADD COLUMN `special_discount_id` BIGINT UNSIGNED NULL AFTER `membership_value`,
ADD COLUMN `special_discount_document` VARCHAR(500) NULL AFTER `special_discount_id`,
ADD COLUMN `discount_amount` DECIMAL(15,2) NULL AFTER `special_discount_document`,
ADD INDEX `idx_members_special_discount` (`special_discount_id`),
ADD CONSTRAINT `fk_members_special_discount` FOREIGN KEY (`special_discount_id`) REFERENCES `special_discounts`(`id`) ON DELETE SET NULL
",
'down' => "
ALTER TABLE `members`
DROP FOREIGN KEY `fk_members_special_discount`,
DROP INDEX `idx_members_special_discount`,
DROP COLUMN `special_discount_id`,
DROP COLUMN `special_discount_document`,
DROP COLUMN `discount_amount`
",
];
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