Commit 4461e7e6 authored by Mahmoud Aglan's avatar Mahmoud Aglan

xdsgjdfdhf

parent 6e46794f
...@@ -11,6 +11,7 @@ use App\Core\EventBus; ...@@ -11,6 +11,7 @@ use App\Core\EventBus;
use App\Modules\Children\Models\Child; use App\Modules\Children\Models\Child;
use App\Modules\Children\Services\ChildFeeCalculator; use App\Modules\Children\Services\ChildFeeCalculator;
use App\Modules\Members\Services\NationalIdParser; use App\Modules\Members\Services\NationalIdParser;
use App\Modules\Rules\Services\RuleEngine;
class ChildController extends Controller class ChildController extends Controller
...@@ -83,31 +84,32 @@ class ChildController extends Controller ...@@ -83,31 +84,32 @@ class ChildController extends Controller
$childAge = (int) ($data['age_years'] ?? 0); $childAge = (int) ($data['age_years'] ?? 0);
// Children 16+ require NID $nidRequiredAge = (int) (RuleEngine::getValue('CHILD_NID_REQUIRED_AGE', 'value') ?? 16);
if ($childAge >= 16 && $nid === '') { if ($childAge >= $nidRequiredAge && $nid === '') {
$errors[] = 'الأبناء فوق 16 سنة يجب أن يكون لديهم رقم قومي'; $errors[] = 'الأبناء فوق ' . $nidRequiredAge . ' سنة يجب أن يكون لديهم رقم قومي';
} }
// Age gap validation $minAgeGap = (int) (RuleEngine::getValue('CHILD_MIN_AGE_GAP', 'value') ?? 15);
$maxAgeGap = (int) (RuleEngine::getValue('CHILD_MAX_AGE_GAP', 'value') ?? 60);
if (!empty($data['date_of_birth']) && !empty($member['date_of_birth'])) { if (!empty($data['date_of_birth']) && !empty($member['date_of_birth'])) {
$memberAge = age_from_dob($member['date_of_birth']); $memberAge = age_from_dob($member['date_of_birth']);
$ageGap = $memberAge['years'] - $childAge; $ageGap = $memberAge['years'] - $childAge;
if ($ageGap < 15) { if ($ageGap < $minAgeGap) {
$errors[] = 'الحد الأدنى لفرق السن بين العضو والابن 15 سنة'; $errors[] = 'الحد الأدنى لفرق السن بين العضو والابن ' . $minAgeGap . ' سنة';
} }
if ($ageGap > 60) { if ($ageGap > $maxAgeGap) {
$errors[] = 'الحد الأقصى لفرق السن بين العضو والابن 60 سنة'; $errors[] = 'الحد الأقصى لفرق السن بين العضو والابن ' . $maxAgeGap . ' سنة';
} }
} }
// Stepchild age check $stepchildMaxAge = (int) (RuleEngine::getValue('STEPCHILD_MAX_AGE', 'value') ?? 25);
if (($data['relationship'] ?? '') === 'stepchild' && $childAge >= 25) { $childMaxAge = (int) (RuleEngine::getValue('CHILD_MAX_AGE', 'value') ?? RuleEngine::getValue('CHILD_AUTO_DELETE_AGE', 'value') ?? 25);
$errors[] = 'أبناء الزوج/الزوجة لا يمكن أن يتجاوز عمرهم 25 سنة'; if (($data['relationship'] ?? '') === 'stepchild' && $childAge >= $stepchildMaxAge) {
$errors[] = 'أبناء الزوج/الزوجة لا يمكن أن يتجاوز عمرهم ' . $stepchildMaxAge . ' سنة';
} }
// Check age 25+ not accepted if ($childAge >= $childMaxAge && ($data['relationship'] ?? '') !== 'stepchild') {
if ($childAge >= 25 && ($data['relationship'] ?? '') !== 'stepchild') { $errors[] = 'لا يمكن إضافة أبناء فوق ' . $childMaxAge . ' سنة';
$errors[] = 'لا يمكن إضافة أبناء فوق 25 سنة';
} }
if (!empty($errors)) { if (!empty($errors)) {
......
...@@ -38,8 +38,8 @@ final class PricingEngine ...@@ -38,8 +38,8 @@ final class PricingEngine
public static function calculateChildFee(string $membershipValue, int $childAge, int $childOrder): array public static function calculateChildFee(string $membershipValue, int $childAge, int $childOrder): array
{ {
$maxIncluded = RuleEngine::getValue('CHILD_INCLUDED_MAX_COUNT', 'value') ?? 3; $maxIncluded = (int) (RuleEngine::getValue('INITIAL_FREE_CHILDREN_COUNT', 'value') ?? RuleEngine::getValue('CHILD_INCLUDED_MAX_COUNT', 'value') ?? 2);
$maxIncludedAge = RuleEngine::getValue('CHILD_INCLUDED_MAX_AGE', 'value') ?? 18; $maxIncludedAge = (int) (RuleEngine::getValue('CHILD_INCLUDED_MAX_AGE', 'value') ?? 18);
if ($childAge < $maxIncludedAge && $childOrder <= $maxIncluded) { if ($childAge < $maxIncludedAge && $childOrder <= $maxIncluded) {
return ['fee' => '0.00', 'rule_applied' => 'included', 'percentage' => '0.00', 'classification' => 'included']; return ['fee' => '0.00', 'rule_applied' => 'included', 'percentage' => '0.00', 'classification' => 'included'];
......
...@@ -11,6 +11,7 @@ use App\Core\EventBus; ...@@ -11,6 +11,7 @@ use App\Core\EventBus;
use App\Modules\Spouses\Models\Spouse; use App\Modules\Spouses\Models\Spouse;
use App\Modules\Spouses\Services\SpouseFeeCalculator; use App\Modules\Spouses\Services\SpouseFeeCalculator;
use App\Modules\Members\Services\NationalIdParser; use App\Modules\Members\Services\NationalIdParser;
use App\Modules\Rules\Services\RuleEngine;
class SpouseController extends Controller class SpouseController extends Controller
...@@ -122,9 +123,9 @@ class SpouseController extends Controller ...@@ -122,9 +123,9 @@ class SpouseController extends Controller
$data['age_months'] = $age['months']; $data['age_months'] = $age['months'];
} }
// ── Minimum age check (18+) ── $spouseMinAge = (int) (RuleEngine::getValue('SPOUSE_MIN_AGE', 'value') ?? 18);
if ((int) ($data['age_years'] ?? 0) < 18) { if ((int) ($data['age_years'] ?? 0) < $spouseMinAge) {
$errors[] = 'الحد الأدنى للسن 18 سنة'; $errors[] = 'الحد الأدنى للسن ' . $spouseMinAge . ' سنة';
} }
if (!empty($errors)) { if (!empty($errors)) {
......
...@@ -5,6 +5,7 @@ namespace App\Modules\Spouses\Models; ...@@ -5,6 +5,7 @@ namespace App\Modules\Spouses\Models;
use App\Core\Model; use App\Core\Model;
use App\Core\App; use App\Core\App;
use App\Modules\Rules\Services\RuleEngine;
class Spouse extends Model class Spouse extends Model
{ {
...@@ -109,9 +110,10 @@ class Spouse extends Model ...@@ -109,9 +110,10 @@ class Spouse extends Model
*/ */
public static function getMaxSpouses(string $memberGender): int public static function getMaxSpouses(string $memberGender): int
{ {
// Female member: max 1 husband if ($memberGender === 'female') {
// Male member: max 4 wives (as per regulations) return (int) (RuleEngine::getValue('MAX_SPOUSES_FEMALE_MEMBER', 'value') ?? 1);
return $memberGender === 'female' ? 1 : 4; }
return (int) (RuleEngine::getValue('MAX_SPOUSES_MALE_MEMBER', 'value') ?? 4);
} }
/** /**
......
<?php
declare(strict_types=1);
/**
* Phase 40: Master Document Compliance
*
* Ensures all business_rules values match the master document.
* Adds missing rules that were previously hardcoded.
* After this seed, all variable values are editable via /rules admin.
*/
return function (\App\Core\Database $db) {
$now = date('Y-m-d H:i:s');
$today = date('Y-m-d');
// ═══════════════════════════════════════════════════════════════
// PART 1: Fix existing rules to match master document
// ═══════════════════════════════════════════════════════════════
$corrections = [
// Master document: "First 3 children under 18 years: INCLUDED"
'CHILD_INCLUDED_MAX_COUNT' => ['current_value_json' => '{"value":3}'],
'INITIAL_FREE_CHILDREN_COUNT' => ['current_value_json' => '{"value":3}'],
];
foreach ($corrections as $code => $update) {
$existing = $db->selectOne(
"SELECT id, current_value_json, version FROM business_rules WHERE rule_code = ? AND branch_id IS NULL AND is_active = 1",
[$code]
);
if (!$existing) {
continue;
}
$currentVal = $existing['current_value_json'];
$newVal = $update['current_value_json'];
if ($currentVal === $newVal) {
continue;
}
$newVersion = (int) $existing['version'] + 1;
$db->insert('rule_versions', [
'rule_id' => (int) $existing['id'],
'version_number' => $newVersion,
'old_value_json' => $currentVal,
'new_value_json' => $newVal,
'changed_by' => null,
'changed_at' => $now,
'change_reason' => 'تصحيح طبقاً للائحة الرئيسية',
]);
$db->update('business_rules', [
'current_value_json' => $newVal,
'version' => $newVersion,
'updated_at' => $now,
], '`id` = ?', [(int) $existing['id']]);
}
// ═══════════════════════════════════════════════════════════════
// PART 2: Add missing rules (previously hardcoded)
// ═══════════════════════════════════════════════════════════════
$newRules = [
// ── Spouse limits ──
[
'rule_code' => 'MAX_SPOUSES_MALE_MEMBER',
'category' => 'spouse_fee',
'name_ar' => 'الحد الأقصى لعدد الزوجات (عضو ذكر)',
'name_en' => 'Max Spouses for Male Member',
'data_type' => 'integer',
'current_value_json' => '{"value":4}',
'parameters_json' => '{"value":"integer"}',
],
[
'rule_code' => 'MAX_SPOUSES_FEMALE_MEMBER',
'category' => 'spouse_fee',
'name_ar' => 'الحد الأقصى لعدد الأزواج (عضوة أنثى)',
'name_en' => 'Max Spouses for Female Member',
'data_type' => 'integer',
'current_value_json' => '{"value":1}',
'parameters_json' => '{"value":"integer"}',
],
[
'rule_code' => 'SPOUSE_MIN_AGE',
'category' => 'spouse_fee',
'name_ar' => 'الحد الأدنى لسن الزوج/الزوجة',
'name_en' => 'Minimum Spouse Age',
'data_type' => 'integer',
'current_value_json' => '{"value":18}',
'parameters_json' => '{"value":"integer"}',
],
// ── Child validation limits ──
[
'rule_code' => 'CHILD_NID_REQUIRED_AGE',
'category' => 'age',
'name_ar' => 'سن إلزام الرقم القومي للأبناء',
'name_en' => 'Child NID Required Age',
'data_type' => 'integer',
'current_value_json' => '{"value":16}',
'parameters_json' => '{"value":"integer"}',
],
[
'rule_code' => 'CHILD_MIN_AGE_GAP',
'category' => 'age',
'name_ar' => 'الحد الأدنى لفرق السن بين العضو والابن',
'name_en' => 'Min Parent-Child Age Gap',
'data_type' => 'integer',
'current_value_json' => '{"value":15}',
'parameters_json' => '{"value":"integer"}',
],
[
'rule_code' => 'CHILD_MAX_AGE_GAP',
'category' => 'age',
'name_ar' => 'الحد الأقصى لفرق السن بين العضو والابن',
'name_en' => 'Max Parent-Child Age Gap',
'data_type' => 'integer',
'current_value_json' => '{"value":60}',
'parameters_json' => '{"value":"integer"}',
],
[
'rule_code' => 'CHILD_MAX_AGE',
'category' => 'age',
'name_ar' => 'الحد الأقصى لسن الابن (غير مؤقت)',
'name_en' => 'Max Child Age (non-temp)',
'data_type' => 'integer',
'current_value_json' => '{"value":25}',
'parameters_json' => '{"value":"integer"}',
],
// ── Death/Transfer rules ──
[
'rule_code' => 'DEATH_TRANSFER_WINDOW_MONTHS',
'category' => 'death',
'name_ar' => 'مهلة نقل العضوية بعد الوفاة (بالأشهر)',
'name_en' => 'Death Transfer Window Months',
'data_type' => 'integer',
'current_value_json' => '{"value":12}',
'parameters_json' => '{"value":"integer"}',
],
// ── Annual subscription due date ──
[
'rule_code' => 'ANNUAL_SUB_DUE_MONTH',
'category' => 'workflow',
'name_ar' => 'شهر استحقاق الاشتراك السنوي',
'name_en' => 'Annual Subscription Due Month',
'data_type' => 'integer',
'current_value_json' => '{"value":7}',
'parameters_json' => '{"value":"integer"}',
],
// ── Carnet replacement fee ──
[
'rule_code' => 'CARNET_REPLACEMENT_FEE',
'category' => 'financial',
'name_ar' => 'رسوم بدل فاقد الكارنيه',
'name_en' => 'Carnet Replacement Fee',
'data_type' => 'amount',
'current_value_json' => '{"amount":"200.00"}',
'parameters_json' => '{"amount":"decimal"}',
],
// ── Freeze (تجميد) rules ──
[
'rule_code' => 'FREEZE_MAX_YEARS',
'category' => 'workflow',
'name_ar' => 'أقصى مدة تجميد العضوية (سنوات)',
'name_en' => 'Max Freeze Duration Years',
'data_type' => 'integer',
'current_value_json' => '{"value":3}',
'parameters_json' => '{"value":"integer"}',
],
[
'rule_code' => 'FREEZE_ANNUAL_FEE_PERCENTAGE',
'category' => 'workflow',
'name_ar' => 'نسبة رسوم التجميد السنوية من الاشتراك',
'name_en' => 'Freeze Annual Fee Percentage',
'data_type' => 'percentage',
'current_value_json' => '{"percentage":"50.00"}',
'parameters_json' => '{"percentage":"decimal"}',
],
// ── Seasonal membership limits ──
[
'rule_code' => 'SEASONAL_FEE_PERCENTAGE',
'category' => 'temporary',
'name_ar' => 'نسبة رسوم العضوية الموسمية',
'name_en' => 'Seasonal Fee Percentage',
'data_type' => 'percentage',
'current_value_json' => '{"percentage":"5.00"}',
'parameters_json' => '{"percentage":"decimal"}',
],
// ── Board approval thresholds ──
[
'rule_code' => 'BOARD_APPROVAL_THRESHOLD',
'category' => 'workflow',
'name_ar' => 'حد المبلغ الذي يتطلب موافقة مجلس الإدارة',
'name_en' => 'Board Approval Amount Threshold',
'data_type' => 'amount',
'current_value_json' => '{"amount":"50000.00"}',
'parameters_json' => '{"amount":"decimal"}',
],
];
foreach ($newRules as $rule) {
$exists = $db->selectOne(
"SELECT id FROM business_rules WHERE rule_code = ? AND branch_id IS NULL",
[$rule['rule_code']]
);
if ($exists) {
continue;
}
$db->insert('business_rules', array_merge($rule, [
'branch_id' => null,
'effective_from' => $today,
'effective_to' => null,
'version' => 1,
'is_active' => 1,
'created_at' => $now,
'updated_at' => $now,
]));
}
};
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