Commit b4b78902 authored by Mahmoud Aglan's avatar Mahmoud Aglan

Add board approval workflow to divorce module with fee transparency

- Divorce requests now go through board review before payment
- Board sets fee percentage based on qualification + divorce certificate
- Total fee = (percentage × membership value) + 570 form fee + annual subscription
- New member page shows old/new membership number transfer link
- Full workflow: submit → board_review → board_approved → payment → complete
Co-Authored-By: 's avatarClaude Opus 4.6 <noreply@anthropic.com>
parent 49388c23
......@@ -16,10 +16,14 @@ class DivorceCase extends Model
protected static bool $dispatchEvents = true;
protected static array $fillable = [
'member_id', 'spouse_id', 'divorce_date', 'divorce_case_type',
'member_id', 'spouse_id', 'divorce_date', 'divorce_certificate_path',
'spouse_qualification_id', 'divorce_case_type',
'request_date', 'membership_acquisition_date', 'years_of_membership',
'has_children_on_membership', 'fee_percentage', 'fee_amount',
'annual_subscription_fee', 'form_fee', 'total_fee',
'board_decision_number', 'board_decision_date', 'board_notes',
'spouse_new_member_id', 'spouse_new_membership_number',
'original_member_id', 'original_membership_number',
'children_assignment_json', 'archive_snapshot_id',
'workflow_instance_id', 'status', 'notes',
];
......
......@@ -6,6 +6,8 @@ return [
['GET', '/divorce/create/{memberId}', 'Divorce\Controllers\DivorceController@create', ['auth'], 'transfer.initiate'],
['POST', '/divorce/store/{memberId}', 'Divorce\Controllers\DivorceController@store', ['auth', 'csrf'], 'transfer.initiate'],
['GET', '/divorce/{id}', 'Divorce\Controllers\DivorceController@show', ['auth'], 'transfer.view'],
['POST', '/divorce/{id}/board-approve', 'Divorce\Controllers\DivorceController@boardApprove', ['auth', 'csrf'], 'transfer.approve'],
['POST', '/divorce/{id}/board-reject', 'Divorce\Controllers\DivorceController@boardReject', ['auth', 'csrf'], 'transfer.approve'],
['POST', '/divorce/{id}/pay', 'Divorce\Controllers\DivorceController@pay', ['auth', 'csrf'], 'payment.collect'],
['POST', '/divorce/{id}/complete', 'Divorce\Controllers\DivorceController@complete',['auth', 'csrf'], 'transfer.approve'],
['POST', '/divorce/{id}/complete', 'Divorce\Controllers\DivorceController@complete', ['auth', 'csrf'], 'transfer.approve'],
];
\ No newline at end of file
......@@ -5,11 +5,35 @@ namespace App\Modules\Divorce\Services;
use App\Core\App;
use App\Modules\Rules\Services\RuleEngine;
use App\Modules\ServiceCatalog\Models\ServicePrice;
final class DivorceFeeCalculator
{
public static function getFormFee(): string
{
$feeData = RuleEngine::get('FORM_TRANSFER_FEE');
if ($feeData && isset($feeData['amount'])) {
return $feeData['amount'];
}
return ServicePrice::getPrice('SVC_ADDITION_FORM', '570.00');
}
public static function getAnnualSubscription(): string
{
$month = (int) date('n');
$year = (int) date('Y');
$fy = $month >= 7 ? $year . '/' . ($year + 1) : ($year - 1) . '/' . $year;
$ratesData = RuleEngine::get('membership.annual_rates.' . $fy);
$childRate = $ratesData['child'] ?? '222';
$devFee = $ratesData['dev'] ?? '35';
return bcadd($childRate, $devFee, 2);
}
/**
* Determine divorce case type and calculate fee.
* Validate eligibility and determine case type (without calculating final fee —
* the board sets the percentage).
*/
public static function calculate(int $memberId, int $spouseId, string $divorceDate): array
{
......@@ -52,46 +76,18 @@ final class DivorceFeeCalculator
return ['success' => false, 'error' => "الحد الأدنى لسنوات العضوية {$minYears} سنوات (يُعفى في حالة وجود أبناء)"];
}
// Determine case type and fee
$caseType = 'joined_after'; // default: most common
$feePercentage = '50.00';
$feeType = 'acquired_member';
// Case 1: Both working before marriage
// Simplified detection: if spouse join_date == member creation date (same form)
// Determine case type
$caseType = 'joined_after';
$memberCreatedDate = substr($memberCreated, 0, 10);
$spouseJoinDateStr = substr($spouseJoinDate, 0, 10);
if ($memberCreatedDate === $spouseJoinDateStr) {
// Could be Case 2: same form
$sameFormData = RuleEngine::get('DIVORCE_SAME_FORM_FEE');
$caseType = 'same_form';
$feePercentage = $sameFormData['percentage'] ?? '10.00';
$feeType = $sameFormData['treat_as'] ?? 'membership_basis';
} else {
// Case 3: joined after
$joinedAfterData = RuleEngine::get('DIVORCE_JOINED_AFTER_FEE');
$caseType = 'joined_after';
$feePercentage = $joinedAfterData['percentage'] ?? '50.00';
$feeType = $joinedAfterData['treat_as'] ?? 'acquired_member';
}
$feeAmount = bcdiv(bcmul($membershipValue, $feePercentage, 4), '100', 2);
// Form fee
$formFeeData = RuleEngine::get('FORM_TRANSFER_FEE');
$formFee = $formFeeData['amount'] ?? '570.00';
$totalFee = bcadd($feeAmount, $formFee, 2);
return [
'success' => true,
'case_type' => $caseType,
'fee_type' => $feeType,
'fee_percentage' => $feePercentage,
'fee_amount' => $feeAmount,
'form_fee' => $formFee,
'total_fee' => $totalFee,
'membership_value' => $membershipValue,
'years_of_membership' => $yearsMembership,
'has_children' => $hasChildren,
......
<?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="/divorce/store/<?= (int) $member['id'] ?>">
<div class="card" style="margin-bottom:15px;padding:15px;display:flex;justify-content:space-between;align-items:center;">
<div>
<strong>العضو:</strong> <?= e($member['full_name_ar']) ?>
&nbsp;|&nbsp; <strong>رقم العضوية:</strong> <?= e($member['membership_number'] ?? 'لم يُحدد') ?>
&nbsp;|&nbsp; <strong>قيمة العضوية:</strong> <span style="color:#0D7377;font-weight:700;"><?= money($member['membership_value'] ?? '0') ?></span>
</div>
<a href="/members/<?= (int) $member['id'] ?>" class="btn btn-outline">← العودة للعضو</a>
</div>
<form method="POST" action="/divorce/store/<?= (int) $member['id'] ?>" enctype="multipart/form-data">
<?= csrf_field() ?>
<div class="card" style="padding:20px;margin-bottom:20px;">
<div style="display:grid;grid-template-columns:1fr 1fr;gap:15px;">
<div class="card" style="margin-bottom:20px;">
<div style="padding:15px 20px;border-bottom:1px solid #E5E7EB;"><h3 style="margin:0;color:#DC2626;">بيانات الطلاق</h3></div>
<div style="padding:20px;display:grid;grid-template-columns:1fr 1fr;gap:15px;">
<div class="form-group">
<label class="form-label">الزوج/الزوجة <span style="color:#DC2626;">*</span></label>
<select name="spouse_id" class="form-select" required>
......@@ -16,6 +28,21 @@
<label class="form-label">تاريخ الطلاق <span style="color:#DC2626;">*</span></label>
<input type="date" name="divorce_date" class="form-input" required max="<?= e(date('Y-m-d')) ?>">
</div>
<div class="form-group">
<label class="form-label">قسيمة الطلاق (صورة/ملف) <span style="color:#DC2626;">*</span></label>
<input type="file" name="divorce_certificate" class="form-input" required accept=".pdf,.jpg,.jpeg,.png">
<small style="color:#6B7280;">PDF أو صورة — الحد الأقصى 5 ميجا</small>
</div>
<div class="form-group">
<label class="form-label">المؤهل <span style="color:#DC2626;">*</span></label>
<select name="spouse_qualification_id" class="form-select" required>
<option value="">-- اختر المؤهل --</option>
<?php foreach ($qualifications as $q): ?>
<option value="<?= (int) $q['id'] ?>"><?= e($q['name_ar']) ?></option>
<?php endforeach; ?>
</select>
<small style="color:#6B7280;">المؤهل يحدد نسبة الرسوم من قيمة العضوية</small>
</div>
<div class="form-group" style="grid-column:1/-1;">
<label class="form-label">ملاحظات</label>
<textarea name="notes" class="form-textarea" rows="3"></textarea>
......@@ -23,23 +50,21 @@
</div>
</div>
<!-- Fee & Payment -->
<div class="card" style="padding:20px;margin-bottom:20px;background:#FFF7ED;border:2px solid #F59E0B;">
<h4 style="margin:0 0 15px;color:#D97706;">رسوم الإجراء</h4>
<p style="font-size:13px;color:#6B7280;margin-bottom:15px;">سيتم احتساب الرسوم تلقائياً (رسوم استمارة + نسبة من قيمة العضوية حسب نوع الحالة).<br>قيمة العضوية: <strong style="color:#0D7377;"><?= money($member['membership_value'] ?? '0') ?></strong></p>
<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>
<select name="payment_method" class="form-select" required>
<option value="cash">نقدي</option>
<option value="visa">فيزا</option>
<option value="bank_transfer">تحويل بنكي</option>
</select>
</div>
</div>
<div class="card" style="padding:20px;margin-bottom:20px;background:#FEF3C7;border:2px solid #F59E0B;">
<h4 style="margin:0 0 10px;color:#D97706;">مسار الإجراء</h4>
<ol style="margin:0;padding-right:20px;font-size:13px;color:#6B7280;line-height:2;">
<li>تقديم الطلب مع قسيمة الطلاق والمؤهل</li>
<li>إرسال الطلب لمجلس الأمناء للمراجعة</li>
<li>مجلس الأمناء يحدد نسبة الرسوم من قيمة العضوية حسب المؤهل</li>
<li>احتساب الرسوم: (النسبة × قيمة العضوية) + استمارة 570 + اشتراك سنوي</li>
<li>إرسال المبلغ للخزينة والسداد</li>
<li>إنشاء عضوية جديدة مستقلة للمطلقة</li>
</ol>
</div>
<button type="submit" class="btn btn-primary" onclick="return confirm('سيتم تسجيل حالة الطلاق وتحصيل الرسوم. متأكد؟')">تسجيل الطلاق وتحصيل الرسوم</button>
<div style="display:flex;gap:10px;">
<button type="submit" class="btn btn-primary" onclick="return confirm('سيتم تسجيل طلب الطلاق وإرساله لمجلس الأمناء. متأكد؟')">تقديم طلب الطلاق</button>
<a href="/members/<?= (int) $member['id'] ?>" class="btn btn-outline">إلغاء</a>
</div>
</form>
<?php $__template->endSection(); ?>
\ No newline at end of file
......@@ -14,8 +14,12 @@
<td><?= e($r['spouse_name']) ?></td>
<td style="font-size:13px;"><?= e($r['divorce_date']) ?></td>
<td style="font-size:13px;"><?= e(\App\Modules\Divorce\Models\DivorceCase::getCaseTypeLabel($r['divorce_case_type'])) ?></td>
<td style="font-weight:600;"><?= money($r['fee_amount'] ?? '0') ?></td>
<td><span style="color:<?= $r['status'] === 'completed' ? '#059669' : '#D97706' ?>;font-weight:600;"><?= $r['status'] === 'completed' ? 'مكتمل' : ($r['status'] === 'submitted' ? 'مقدّم' : $r['status']) ?></span></td>
<td style="font-weight:600;"><?= money($r['total_fee'] ?? $r['fee_amount'] ?? '0') ?></td>
<?php
$sColor = match($r['status']) { 'completed' => '#059669', 'fee_paid' => '#7C3AED', 'board_approved' => '#2563EB', 'rejected' => '#DC2626', default => '#D97706' };
$sLabel = match($r['status']) { 'completed' => 'مكتمل', 'fee_paid' => 'تم السداد', 'board_approved' => 'معتمد', 'board_review' => 'في انتظار المجلس', 'rejected' => 'مرفوض', 'submitted' => 'مقدّم', default => $r['status'] };
?>
<td><span style="color:<?= $sColor ?>;font-weight:600;"><?= $sLabel ?></span></td>
<td><a href="/divorce/<?= (int) $r['id'] ?>" class="btn btn-sm btn-outline">عرض</a></td>
</tr>
<?php endforeach; ?>
......
This diff is collapsed.
......@@ -202,6 +202,17 @@ class MemberController extends Controller
}
}
// Check if this member was created via divorce transfer
$divorceTransfer = null;
if ($member->transferred_from_divorce_id) {
$divorceTransfer = $db->selectOne(
"SELECT dc.id, dc.original_membership_number, m.full_name_ar as original_member_name, m.id as original_member_id
FROM divorce_cases dc LEFT JOIN members m ON m.id = dc.original_member_id
WHERE dc.id = ?",
[(int) $member->transferred_from_divorce_id]
);
}
return $this->view('Members.Views.show', [
'member' => $member,
'branchName' => $branch['name_ar'] ?? '—',
......@@ -217,6 +228,7 @@ class MemberController extends Controller
'pendingMembership' => $pendingMembership,
'pendingAdditions' => $pendingAdditions,
'isSuperAdmin' => self::isSuperAdmin(),
'divorceTransfer' => $divorceTransfer,
]);
}
......
......@@ -102,6 +102,9 @@ $canEdit = !$isLocked || ($isSuperAdmin ?? false);
<?php if ($member->full_name_en): ?><div style="color:#6B7280;font-size:14px;margin-bottom:8px;"><?= e($member->full_name_en) ?></div><?php endif; ?>
<div style="display:flex;gap:15px;flex-wrap:wrap;font-size:13px;color:#6B7280;margin-top:10px;">
<?php if ($member->membership_number): ?><span>🪪 عضوية: <strong style="color:#0D7377;font-size:16px;"><?= e($member->membership_number) ?></strong></span><?php endif; ?>
<?php if (!empty($divorceTransfer)): ?>
<span style="background:#FEF3C7;padding:2px 8px;border-radius:10px;font-size:11px;">محوّل من عضوية: <a href="/members/<?= (int) $divorceTransfer['original_member_id'] ?>" style="color:#D97706;font-weight:700;"><?= e($divorceTransfer['original_membership_number']) ?></a> (<?= e($divorceTransfer['original_member_name'] ?? '') ?>)</span>
<?php endif; ?>
<span>📋 استمارة: <strong style="color:#D97706;"><?= e($member->form_number ?? '—') ?></strong></span>
<span>🏢 <?= e($branchName) ?></span>
<span><?= $member->gender === 'male' ? '👨' : '👩' ?> <?= $member->getGenderLabel() ?></span>
......
<?php
declare(strict_types=1);
return function (\App\Core\Database $db): void {
$table = 'divorce_cases';
$columnsToAdd = [
'divorce_certificate_path' => "VARCHAR(500) NULL AFTER `divorce_date`",
'spouse_qualification_id' => "BIGINT UNSIGNED NULL AFTER `divorce_certificate_path`",
'board_decision_number' => "VARCHAR(50) NULL AFTER `fee_amount`",
'board_decision_date' => "DATE NULL AFTER `board_decision_number`",
'board_notes' => "TEXT NULL AFTER `board_decision_date`",
'annual_subscription_fee' => "DECIMAL(15,2) NULL DEFAULT 0 AFTER `fee_amount`",
'form_fee' => "DECIMAL(15,2) NULL DEFAULT 0 AFTER `annual_subscription_fee`",
'total_fee' => "DECIMAL(15,2) NULL DEFAULT 0 AFTER `form_fee`",
'original_member_id' => "BIGINT UNSIGNED NULL AFTER `spouse_new_membership_number`",
'original_membership_number' => "VARCHAR(20) NULL AFTER `original_member_id`",
];
foreach ($columnsToAdd as $col => $definition) {
$exists = $db->selectOne(
"SELECT 1 FROM information_schema.columns WHERE table_schema = DATABASE() AND table_name = ? AND column_name = ?",
[$table, $col]
);
if (!$exists) {
$db->raw("ALTER TABLE `{$table}` ADD COLUMN `{$col}` {$definition}");
}
}
// Add transferred_from_divorce_id to members table for linking back
$exists = $db->selectOne(
"SELECT 1 FROM information_schema.columns WHERE table_schema = DATABASE() AND table_name = 'members' AND column_name = 'transferred_from_divorce_id'",
[]
);
if (!$exists) {
$db->raw("ALTER TABLE `members` ADD COLUMN `transferred_from_divorce_id` BIGINT UNSIGNED NULL");
}
$exists = $db->selectOne(
"SELECT 1 FROM information_schema.columns WHERE table_schema = DATABASE() AND table_name = 'members' AND column_name = 'original_membership_number'",
[]
);
if (!$exists) {
$db->raw("ALTER TABLE `members` ADD COLUMN `original_membership_number` VARCHAR(20) NULL");
}
};
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