Commit 3f42fa01 authored by Mahmoud Aglan's avatar Mahmoud Aglan

feat(pricing): implement regulatory discounts (Articles 97-102, 110)

Full-cycle implementation of club bylaw discount rules:
- Art 97: Cross-branch member discounts (50% Sheraton6th Oct, 25% →Admin Capital)
- Art 98: Government employees 50%, Ministry of Youth 62.5% at Admin Capital
- Art 99: Ministry of Youth 25% at Sheraton/6th Oct
- Art 100: Board of Trustees 50% + 2yr interest-free installment
- Art 101: Ministry employees installment-only (no discount)
- Art 102: Club employees (5+ yrs) up to 15%
- Art 110: Group membership tiered (5-10→3%, 11-20→7%, 21+→10%)

Includes: migration, seed data, model, service with eligibility engine,
controller (CRUD + eligibility check API + application workflow),
views (index, form, applications), routes, permissions, menu entry,
and PricingEngine integration.
Co-Authored-By: 's avatarClaude Opus 4.6 <noreply@anthropic.com>
parent 31130c75
<?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\RegulatoryDiscount;
use App\Modules\Pricing\Services\RegulatoryDiscountService;
class RegulatoryDiscountController extends Controller
{
public function index(Request $request): Response
{
$filters = [
'search' => trim((string) $request->get('q', '')),
'is_active' => $request->get('is_active', ''),
'eligibility_type' => $request->get('eligibility_type', ''),
];
$page = max(1, (int) $request->get('page', 1));
$result = RegulatoryDiscount::search($filters, 25, $page);
$db = App::getInstance()->db();
$branches = $db->select("SELECT id, name_ar FROM branches WHERE is_active = 1 ORDER BY name_ar");
return $this->view('Pricing.Views.regulatory_discounts.index', [
'rows' => $result['data'],
'pagination' => $result['pagination'],
'filters' => $filters,
'branches' => $branches,
]);
}
public function create(Request $request): Response
{
$db = App::getInstance()->db();
$branches = $db->select("SELECT id, name_ar FROM branches WHERE is_active = 1 ORDER BY name_ar");
return $this->view('Pricing.Views.regulatory_discounts.form', [
'discount' => null,
'branches' => $branches,
'tiers' => [],
]);
}
public function store(Request $request): Response
{
$data = $this->extractFormData($request);
$error = $this->validateData($data);
if ($error) {
return $this->redirect('/pricing/regulatory-discounts/create')->withError($error);
}
$db = App::getInstance()->db();
$userId = session()->get('user_id');
$insertData = $this->buildInsertArray($data);
$insertData['created_by'] = $userId;
$id = $db->insert('regulatory_discounts', $insertData);
// Save group tiers if applicable
if ($data['eligibility_type'] === 'group_membership') {
$this->saveGroupTiers($db, (int) $id, $request);
}
return $this->redirect('/pricing/regulatory-discounts')->withSuccess('تم إضافة الخصم اللائحي بنجاح');
}
public function edit(Request $request, string $id): Response
{
$db = App::getInstance()->db();
$discount = $db->selectOne("SELECT * FROM regulatory_discounts WHERE id = ?", [(int) $id]);
if (!$discount) {
return $this->redirect('/pricing/regulatory-discounts')->withError('الخصم غير موجود');
}
$branches = $db->select("SELECT id, name_ar FROM branches WHERE is_active = 1 ORDER BY name_ar");
$tiers = $db->select("SELECT * FROM regulatory_discount_group_tiers WHERE regulatory_discount_id = ? ORDER BY min_quantity", [(int) $id]);
return $this->view('Pricing.Views.regulatory_discounts.form', [
'discount' => $discount,
'branches' => $branches,
'tiers' => $tiers,
]);
}
public function update(Request $request, string $id): Response
{
$db = App::getInstance()->db();
$discount = $db->selectOne("SELECT * FROM regulatory_discounts WHERE id = ?", [(int) $id]);
if (!$discount) {
return $this->redirect('/pricing/regulatory-discounts')->withError('الخصم غير موجود');
}
$data = $this->extractFormData($request);
$data['is_active'] = (int) $request->post('is_active', 1);
$error = $this->validateData($data);
if ($error) {
return $this->redirect("/pricing/regulatory-discounts/{$id}/edit")->withError($error);
}
$userId = session()->get('user_id');
$updateData = $this->buildInsertArray($data);
$updateData['is_active'] = $data['is_active'];
$updateData['updated_by'] = $userId;
$db->update('regulatory_discounts', $updateData, 'id = ?', [(int) $id]);
if ($data['eligibility_type'] === 'group_membership') {
$db->delete('regulatory_discount_group_tiers', 'regulatory_discount_id = ?', [(int) $id]);
$this->saveGroupTiers($db, (int) $id, $request);
}
return $this->redirect('/pricing/regulatory-discounts')->withSuccess('تم تحديث الخصم اللائحي');
}
public function toggleActive(Request $request, string $id): Response
{
$db = App::getInstance()->db();
$discount = $db->selectOne("SELECT * FROM regulatory_discounts WHERE id = ?", [(int) $id]);
if (!$discount) {
return $this->redirect('/pricing/regulatory-discounts')->withError('الخصم غير موجود');
}
$newActive = $discount['is_active'] ? 0 : 1;
$db->update('regulatory_discounts', [
'is_active' => $newActive,
'updated_at' => date('Y-m-d H:i:s'),
], 'id = ?', [(int) $id]);
return $this->redirect('/pricing/regulatory-discounts')->withSuccess($newActive ? 'تم تفعيل الخصم' : 'تم تعطيل الخصم');
}
public function checkEligibility(Request $request): Response
{
$context = [
'target_branch_id' => (int) $request->post('target_branch_id', 0),
'source_branch_id' => (int) $request->post('source_branch_id', 0),
'is_government_employee' => (bool) $request->post('is_government_employee', false),
'is_ministry_youth_employee'=> (bool) $request->post('is_ministry_youth_employee', false),
'is_board_of_trustees' => (bool) $request->post('is_board_of_trustees', false),
'is_club_employee' => (bool) $request->post('is_club_employee', false),
'employee_years' => (int) $request->post('employee_years', 0),
'group_quantity' => (int) $request->post('group_quantity', 0),
'membership_fee' => (string) $request->post('membership_fee', '0'),
];
$results = RegulatoryDiscountService::evaluateEligibility($context);
$best = RegulatoryDiscountService::getBestDiscount($context);
return $this->json([
'success' => true,
'applicable' => $results,
'best' => $best,
]);
}
public function applications(Request $request): Response
{
$db = App::getInstance()->db();
$page = max(1, (int) $request->get('page', 1));
$perPage = 25;
$offset = ($page - 1) * $perPage;
$statusFilter = $request->get('status', '');
$where = "1=1";
$params = [];
if ($statusFilter !== '') {
$where .= " AND rda.status = ?";
$params[] = $statusFilter;
}
$total = (int) $db->selectOne("SELECT COUNT(*) as cnt FROM regulatory_discount_applications rda WHERE {$where}", $params)['cnt'];
$rows = $db->select(
"SELECT rda.*, rd.name_ar as rule_name, rd.article_number FROM regulatory_discount_applications rda LEFT JOIN regulatory_discounts rd ON rd.id = rda.regulatory_discount_id WHERE {$where} ORDER BY rda.created_at DESC LIMIT {$perPage} OFFSET {$offset}",
$params
);
return $this->view('Pricing.Views.regulatory_discounts.applications', [
'rows' => $rows,
'pagination' => ['current_page' => $page, 'last_page' => (int) ceil($total / $perPage), 'total' => $total],
'statusFilter' => $statusFilter,
]);
}
public function approveApplication(Request $request, string $id): Response
{
$userId = (int) session()->get('user_id');
$success = RegulatoryDiscountService::approveApplication((int) $id, $userId);
if (!$success) {
return $this->redirect('/pricing/regulatory-discounts/applications')->withError('لا يمكن اعتماد هذا الطلب');
}
return $this->redirect('/pricing/regulatory-discounts/applications')->withSuccess('تم اعتماد طلب الخصم');
}
public function rejectApplication(Request $request, string $id): Response
{
$db = App::getInstance()->db();
$app = $db->selectOne("SELECT * FROM regulatory_discount_applications WHERE id = ? AND status = 'pending'", [(int) $id]);
if (!$app) {
return $this->redirect('/pricing/regulatory-discounts/applications')->withError('الطلب غير موجود أو ليس معلقاً');
}
$reason = trim((string) $request->post('rejection_reason', ''));
$db->update('regulatory_discount_applications', [
'status' => 'rejected',
'rejection_reason' => $reason ?: null,
'approved_by' => session()->get('user_id'),
'approved_at' => date('Y-m-d H:i:s'),
], 'id = ?', [(int) $id]);
return $this->redirect('/pricing/regulatory-discounts/applications')->withSuccess('تم رفض طلب الخصم');
}
private function extractFormData(Request $request): array
{
return [
'article_number' => trim((string) $request->post('article_number', '')),
'name_ar' => trim((string) $request->post('name_ar', '')),
'name_en' => trim((string) $request->post('name_en', '')) ?: null,
'description' => trim((string) $request->post('description', '')) ?: null,
'discount_percentage' => trim((string) $request->post('discount_percentage', '0')),
'max_discount_percentage' => trim((string) $request->post('max_discount_percentage', '')) ?: null,
'eligibility_type' => $request->post('eligibility_type', 'government_employee'),
'source_branch_id' => ((int) $request->post('source_branch_id', 0)) ?: null,
'target_branch_id' => ((int) $request->post('target_branch_id', 0)) ?: null,
'min_service_years' => ((int) $request->post('min_service_years', 0)) ?: null,
'allows_installment' => (int) $request->post('allows_installment', 0),
'installment_max_years' => ((int) $request->post('installment_max_years', 0)) ?: null,
'installment_interest_rate'=> trim((string) $request->post('installment_interest_rate', '0')),
'applies_to' => $request->post('applies_to', 'membership_fee'),
'effective_from' => trim((string) $request->post('effective_from', '')) ?: null,
'effective_to' => trim((string) $request->post('effective_to', '')) ?: null,
'board_decision_number' => trim((string) $request->post('board_decision_number', '')) ?: null,
'board_decision_date' => trim((string) $request->post('board_decision_date', '')) ?: null,
'notes' => trim((string) $request->post('notes', '')) ?: null,
];
}
private function validateData(array $data): ?string
{
if ($data['name_ar'] === '') {
return 'اسم الخصم مطلوب';
}
if ($data['article_number'] === '') {
return 'رقم المادة مطلوب';
}
$validTypes = ['cross_branch_member', 'government_employee', 'ministry_youth_employee', 'board_of_trustees', 'club_employee', 'group_membership'];
if (!in_array($data['eligibility_type'], $validTypes, true)) {
return 'نوع الاستحقاق غير صالح';
}
if ($data['eligibility_type'] !== 'group_membership') {
$pct = $data['discount_percentage'];
if (bccomp($pct, '0', 2) <= 0 || bccomp($pct, '100', 2) > 0) {
return 'نسبة الخصم يجب أن تكون بين 0.01% و 100%';
}
}
if ($data['eligibility_type'] === 'cross_branch_member') {
if (!$data['source_branch_id'] || !$data['target_branch_id']) {
return 'يجب تحديد فرع المصدر والفرع المستهدف لخصم عبر الفروع';
}
}
return null;
}
private function buildInsertArray(array $data): array
{
return [
'article_number' => $data['article_number'],
'name_ar' => $data['name_ar'],
'name_en' => $data['name_en'],
'description' => $data['description'],
'discount_percentage' => $data['discount_percentage'],
'max_discount_percentage' => $data['max_discount_percentage'],
'eligibility_type' => $data['eligibility_type'],
'source_branch_id' => $data['source_branch_id'],
'target_branch_id' => $data['target_branch_id'],
'min_service_years' => $data['min_service_years'],
'allows_installment' => $data['allows_installment'],
'installment_max_years' => $data['installment_max_years'],
'installment_interest_rate' => $data['installment_interest_rate'],
'applies_to' => $data['applies_to'],
'effective_from' => $data['effective_from'],
'effective_to' => $data['effective_to'],
'board_decision_number' => $data['board_decision_number'],
'board_decision_date' => $data['board_decision_date'],
'notes' => $data['notes'],
];
}
private function saveGroupTiers(\App\Core\Database $db, int $discountId, Request $request): void
{
$mins = $request->post('tier_min', []);
$maxs = $request->post('tier_max', []);
$pcts = $request->post('tier_pct', []);
if (!is_array($mins)) return;
for ($i = 0; $i < count($mins); $i++) {
$min = (int) ($mins[$i] ?? 0);
$max = !empty($maxs[$i]) ? (int) $maxs[$i] : null;
$pct = trim((string) ($pcts[$i] ?? '0'));
if ($min <= 0 || bccomp($pct, '0', 2) <= 0) continue;
$db->insert('regulatory_discount_group_tiers', [
'regulatory_discount_id' => $discountId,
'min_quantity' => $min,
'max_quantity' => $max,
'discount_percentage' => $pct,
]);
}
}
}
<?php
declare(strict_types=1);
namespace App\Modules\Pricing\Models;
use App\Core\Model;
class RegulatoryDiscount extends Model
{
protected string $table = 'regulatory_discounts';
protected array $fillable = [
'article_number', 'name_ar', 'name_en', 'description',
'discount_percentage', 'max_discount_percentage',
'eligibility_type', 'source_branch_id', 'target_branch_id',
'min_service_years', 'allows_installment', 'installment_max_years',
'installment_interest_rate', 'applies_to', 'is_active',
'effective_from', 'effective_to', 'board_decision_number',
'board_decision_date', 'notes', 'created_by', 'updated_by',
];
public static function search(array $filters, int $perPage = 25, int $page = 1): array
{
$query = static::query();
if (!empty($filters['search'])) {
$query->where('name_ar', 'LIKE', '%' . $filters['search'] . '%');
}
if ($filters['is_active'] !== '') {
$query->where('is_active', '=', (int) $filters['is_active']);
}
if (!empty($filters['eligibility_type'])) {
$query->where('eligibility_type', '=', $filters['eligibility_type']);
}
return $query->orderBy('article_number', 'ASC')->paginate($perPage, $page);
}
public static function getActiveByType(string $eligibilityType, ?int $targetBranchId = null): array
{
$db = \App\Core\App::getInstance()->db();
$sql = "SELECT * FROM regulatory_discounts WHERE eligibility_type = ? AND is_active = 1 AND (effective_from IS NULL OR effective_from <= CURDATE()) AND (effective_to IS NULL OR effective_to >= CURDATE())";
$params = [$eligibilityType];
if ($targetBranchId !== null) {
$sql .= " AND (target_branch_id = ? OR target_branch_id IS NULL)";
$params[] = $targetBranchId;
}
return $db->select($sql, $params);
}
}
...@@ -33,4 +33,16 @@ return [ ...@@ -33,4 +33,16 @@ return [
['GET', '/pricing/special-discounts/{id:\d+}/edit', 'Pricing\Controllers\SpecialDiscountController@edit', ['auth'], 'pricing.special_discounts.edit'], ['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+}', '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'], ['POST', '/pricing/special-discounts/{id:\d+}/toggle', 'Pricing\Controllers\SpecialDiscountController@toggleActive', ['auth', 'csrf'], 'pricing.special_discounts.edit'],
// Regulatory Discounts (Articles 97-102, 110)
['GET', '/pricing/regulatory-discounts', 'Pricing\Controllers\RegulatoryDiscountController@index', ['auth'], 'pricing.regulatory_discounts.view'],
['GET', '/pricing/regulatory-discounts/create', 'Pricing\Controllers\RegulatoryDiscountController@create', ['auth'], 'pricing.regulatory_discounts.create'],
['POST', '/pricing/regulatory-discounts', 'Pricing\Controllers\RegulatoryDiscountController@store', ['auth', 'csrf'], 'pricing.regulatory_discounts.create'],
['GET', '/pricing/regulatory-discounts/{id:\d+}/edit', 'Pricing\Controllers\RegulatoryDiscountController@edit', ['auth'], 'pricing.regulatory_discounts.edit'],
['POST', '/pricing/regulatory-discounts/{id:\d+}', 'Pricing\Controllers\RegulatoryDiscountController@update', ['auth', 'csrf'], 'pricing.regulatory_discounts.edit'],
['POST', '/pricing/regulatory-discounts/{id:\d+}/toggle', 'Pricing\Controllers\RegulatoryDiscountController@toggleActive', ['auth', 'csrf'], 'pricing.regulatory_discounts.edit'],
['POST', '/pricing/regulatory-discounts/check-eligibility', 'Pricing\Controllers\RegulatoryDiscountController@checkEligibility', ['auth', 'csrf'], 'pricing.regulatory_discounts.view'],
['GET', '/pricing/regulatory-discounts/applications', 'Pricing\Controllers\RegulatoryDiscountController@applications', ['auth'], 'pricing.regulatory_discounts.approve'],
['POST', '/pricing/regulatory-discounts/applications/{id:\d+}/approve','Pricing\Controllers\RegulatoryDiscountController@approveApplication', ['auth', 'csrf'], 'pricing.regulatory_discounts.approve'],
['POST', '/pricing/regulatory-discounts/applications/{id:\d+}/reject', 'Pricing\Controllers\RegulatoryDiscountController@rejectApplication', ['auth', 'csrf'], 'pricing.regulatory_discounts.approve'],
]; ];
\ No newline at end of file
...@@ -213,6 +213,35 @@ final class PricingEngine ...@@ -213,6 +213,35 @@ final class PricingEngine
]; ];
} }
public static function applyRegulatoryDiscount(string $membershipFee, array $context): array
{
$best = RegulatoryDiscountService::getBestDiscount(array_merge($context, ['membership_fee' => $membershipFee]));
if (!$best) {
return [
'original_amount' => $membershipFee,
'discount_amount' => '0.00',
'final_amount' => $membershipFee,
'discount_percentage' => '0.00',
'rule_applied' => null,
'allows_installment' => false,
];
}
return [
'original_amount' => $best['original_amount'],
'discount_amount' => $best['discount_amount'],
'final_amount' => $best['final_amount'],
'discount_percentage' => $best['discount_percentage'],
'rule_applied' => $best['name_ar'],
'article_number' => $best['article_number'],
'rule_id' => $best['rule_id'],
'allows_installment' => $best['allows_installment'],
'installment_max_years' => $best['installment_max_years'],
'installment_interest_rate' => $best['installment_interest_rate'],
];
}
public static function getServiceFee(string $serviceCode, ?int $branchId = null): ?array public static function getServiceFee(string $serviceCode, ?int $branchId = null): ?array
{ {
$db = App::getInstance()->db(); $db = App::getInstance()->db();
......
<?php
declare(strict_types=1);
namespace App\Modules\Pricing\Services;
use App\Core\App;
use App\Core\Logger;
use App\Modules\Pricing\Models\RegulatoryDiscount;
final class RegulatoryDiscountService
{
/**
* Evaluate all applicable regulatory discounts for a membership application.
*
* @param array $context Keys: target_branch_id, source_branch_id (if existing member),
* is_government_employee, is_ministry_youth_employee,
* is_board_of_trustees, is_club_employee, employee_years,
* group_quantity, membership_fee
* @return array List of applicable discounts with calculated amounts
*/
public static function evaluateEligibility(array $context): array
{
$applicable = [];
$targetBranchId = (int) ($context['target_branch_id'] ?? 0);
$sourceBranchId = (int) ($context['source_branch_id'] ?? 0);
$membershipFee = (string) ($context['membership_fee'] ?? '0');
// Cross-branch member discount (Article 97)
if ($sourceBranchId > 0 && $targetBranchId > 0 && $sourceBranchId !== $targetBranchId) {
$rules = RegulatoryDiscount::getActiveByType('cross_branch_member', $targetBranchId);
foreach ($rules as $rule) {
if ((int) $rule['source_branch_id'] === $sourceBranchId) {
$applicable[] = self::buildResult($rule, $membershipFee);
}
}
}
// Government employee discount (Article 98)
if (!empty($context['is_government_employee'])) {
$rules = RegulatoryDiscount::getActiveByType('government_employee', $targetBranchId);
foreach ($rules as $rule) {
$applicable[] = self::buildResult($rule, $membershipFee);
}
}
// Ministry of Youth employee discount (Articles 98, 99)
if (!empty($context['is_ministry_youth_employee'])) {
$rules = RegulatoryDiscount::getActiveByType('ministry_youth_employee', $targetBranchId);
foreach ($rules as $rule) {
$applicable[] = self::buildResult($rule, $membershipFee);
}
}
// Board of Trustees discount (Article 100)
if (!empty($context['is_board_of_trustees'])) {
$rules = RegulatoryDiscount::getActiveByType('board_of_trustees', $targetBranchId);
foreach ($rules as $rule) {
$applicable[] = self::buildResult($rule, $membershipFee);
}
}
// Club employee discount (Article 102)
if (!empty($context['is_club_employee'])) {
$employeeYears = (int) ($context['employee_years'] ?? 0);
$rules = RegulatoryDiscount::getActiveByType('club_employee', $targetBranchId);
foreach ($rules as $rule) {
$minYears = (int) ($rule['min_service_years'] ?? 0);
if ($employeeYears >= $minYears) {
$applicable[] = self::buildResult($rule, $membershipFee);
}
}
}
// Group membership discount (Article 110)
$groupQty = (int) ($context['group_quantity'] ?? 0);
if ($groupQty >= 5) {
$rules = RegulatoryDiscount::getActiveByType('group_membership', $targetBranchId);
foreach ($rules as $rule) {
$tier = self::getGroupTier((int) $rule['id'], $groupQty);
if ($tier) {
$result = self::buildResult($rule, $membershipFee, (string) $tier['discount_percentage']);
$result['tier'] = $tier;
$result['group_quantity'] = $groupQty;
$applicable[] = $result;
}
}
}
return $applicable;
}
/**
* Get the best single discount for a given context (non-stackable — take highest).
*/
public static function getBestDiscount(array $context): ?array
{
$applicable = self::evaluateEligibility($context);
if (empty($applicable)) {
return null;
}
usort($applicable, function ($a, $b) {
return bccomp($b['discount_amount'], $a['discount_amount'], 2);
});
return $applicable[0];
}
/**
* Create an application record for audit trail.
*/
public static function createApplication(array $data): int
{
$db = App::getInstance()->db();
return $db->insert('regulatory_discount_applications', [
'regulatory_discount_id' => $data['regulatory_discount_id'],
'member_id' => $data['member_id'] ?? null,
'applicant_name' => $data['applicant_name'] ?? null,
'discount_percentage' => $data['discount_percentage'],
'original_amount' => $data['original_amount'],
'discount_amount' => $data['discount_amount'],
'final_amount' => $data['final_amount'],
'status' => $data['status'] ?? 'pending',
'verification_data' => isset($data['verification_data']) ? json_encode($data['verification_data']) : null,
'notes' => $data['notes'] ?? null,
'created_by' => $data['created_by'] ?? null,
]);
}
/**
* Approve a pending discount application.
*/
public static function approveApplication(int $applicationId, int $approvedBy): bool
{
$db = App::getInstance()->db();
$app = $db->selectOne("SELECT * FROM regulatory_discount_applications WHERE id = ?", [$applicationId]);
if (!$app || $app['status'] !== 'pending') {
return false;
}
$db->update('regulatory_discount_applications', [
'status' => 'approved',
'approved_by' => $approvedBy,
'approved_at' => date('Y-m-d H:i:s'),
], 'id = ?', [$applicationId]);
return true;
}
/**
* Verify club employee eligibility by checking HR records.
*/
public static function verifyClubEmployee(int $employeeId): array
{
$db = App::getInstance()->db();
$profile = $db->selectOne(
"SELECT employee_id, first_name_ar, last_name_ar, hire_date, employment_status FROM hr_employee_profiles WHERE employee_id = ? AND is_archived = 0",
[$employeeId]
);
if (!$profile || $profile['employment_status'] !== 'active') {
return ['eligible' => false, 'reason' => 'الموظف غير نشط أو غير موجود'];
}
$hireDate = new \DateTime($profile['hire_date']);
$now = new \DateTime();
$years = (int) $now->diff($hireDate)->y;
return [
'eligible' => true,
'employee_name' => $profile['first_name_ar'] . ' ' . $profile['last_name_ar'],
'hire_date' => $profile['hire_date'],
'service_years' => $years,
];
}
/**
* Verify existing membership in another branch (for cross-branch discount).
*/
public static function verifyCrossBranchMembership(int $memberId, int $sourceBranchId): array
{
$db = App::getInstance()->db();
$member = $db->selectOne(
"SELECT m.id, m.member_number, m.full_name_ar, m.branch_id, b.name_ar as branch_name FROM members m LEFT JOIN branches b ON b.id = m.branch_id WHERE m.id = ? AND m.branch_id = ? AND m.status = 'active'",
[$memberId, $sourceBranchId]
);
if (!$member) {
return ['eligible' => false, 'reason' => 'لا توجد عضوية نشطة في الفرع المصدر'];
}
return [
'eligible' => true,
'member_number' => $member['member_number'],
'member_name' => $member['full_name_ar'],
'branch_name' => $member['branch_name'],
];
}
private static function buildResult(array $rule, string $membershipFee, ?string $overridePercentage = null): array
{
$pct = $overridePercentage ?? $rule['discount_percentage'];
if ($rule['max_discount_percentage'] !== null) {
$pct = min((float) $pct, (float) $rule['max_discount_percentage']);
$pct = number_format($pct, 2, '.', '');
}
$discountAmount = bcmul($membershipFee, bcdiv($pct, '100', 4), 2);
$finalAmount = bcsub($membershipFee, $discountAmount, 2);
return [
'rule_id' => (int) $rule['id'],
'article_number' => $rule['article_number'],
'name_ar' => $rule['name_ar'],
'eligibility_type' => $rule['eligibility_type'],
'discount_percentage' => $pct,
'original_amount' => $membershipFee,
'discount_amount' => $discountAmount,
'final_amount' => $finalAmount,
'allows_installment' => (bool) $rule['allows_installment'],
'installment_max_years' => $rule['installment_max_years'] ? (int) $rule['installment_max_years'] : null,
'installment_interest_rate' => $rule['installment_interest_rate'],
];
}
private static function getGroupTier(int $discountId, int $quantity): ?array
{
$db = App::getInstance()->db();
return $db->selectOne(
"SELECT * FROM regulatory_discount_group_tiers WHERE regulatory_discount_id = ? AND is_active = 1 AND min_quantity <= ? AND (max_quantity IS NULL OR max_quantity >= ?) ORDER BY min_quantity DESC LIMIT 1",
[$discountId, $quantity, $quantity]
);
}
}
<?php $__template->layout('Layout.main'); ?>
<?php $__template->section('title'); ?>طلبات الخصم اللائحي<?php $__template->endSection(); ?>
<?php $__template->section('page_actions'); ?>
<a href="/pricing/regulatory-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'); ?>
<!-- Filters -->
<div class="card" style="margin-bottom:15px;padding:15px;">
<form method="GET" action="/pricing/regulatory-discounts/applications" style="display:flex;gap:10px;align-items:end;">
<div class="form-group" style="min-width:180px;">
<label class="form-label">الحالة</label>
<select name="status" class="form-input">
<option value="">— الكل —</option>
<option value="pending" <?= ($statusFilter ?? '') === 'pending' ? 'selected' : '' ?>>معلق</option>
<option value="approved" <?= ($statusFilter ?? '') === 'approved' ? 'selected' : '' ?>>معتمد</option>
<option value="rejected" <?= ($statusFilter ?? '') === 'rejected' ? 'selected' : '' ?>>مرفوض</option>
</select>
</div>
<button type="submit" class="btn btn-primary" style="padding:8px 20px;">
<i data-lucide="filter" 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="file-check" style="width:18px;height:18px;color:#0D7377;"></i>
<h3 style="margin:0;color:#0D7377;font-size:15px;">طلبات تطبيق الخصم اللائحي</h3>
<?php if (!empty($pagination['total'])): ?>
<span style="margin-right:auto;background:#F3F4F6;padding:2px 10px;border-radius:10px;font-size:12px;color:#6B7280;"><?= $pagination['total'] ?> طلب</span>
<?php endif; ?>
</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>
<th>التاريخ</th>
<th></th>
</tr>
</thead>
<tbody>
<?php foreach ($rows as $row): ?>
<tr>
<td style="color:#9CA3AF;font-size:12px;"><?= (int) $row['id'] ?></td>
<td>
<div style="font-weight:600;font-size:13px;"><?= e($row['rule_name'] ?? '—') ?></div>
<div style="font-size:11px;color:#6B7280;">مادة <?= e($row['article_number'] ?? '') ?></div>
</td>
<td style="font-size:13px;"><?= e($row['applicant_name'] ?? '—') ?></td>
<td style="font-size:13px;direction:ltr;text-align:right;"><?= money($row['original_amount'] ?? 0) ?></td>
<td style="font-weight:700;color:#DC2626;font-size:13px;direction:ltr;text-align:right;">
-<?= money($row['discount_amount'] ?? 0) ?>
<div style="font-size:11px;color:#6B7280;">(<?= e($row['discount_percentage'] ?? 0) ?>%)</div>
</td>
<td style="font-weight:700;color:#059669;font-size:13px;direction:ltr;text-align:right;"><?= money($row['final_amount'] ?? 0) ?></td>
<td>
<?= match($row['status']) {
'pending' => '<span style="display:inline-block;padding:3px 10px;border-radius:10px;font-size:12px;font-weight:600;background:#FEF3C7;color:#92400E;">معلق</span>',
'approved' => '<span style="display:inline-block;padding:3px 10px;border-radius:10px;font-size:12px;font-weight:600;background:#ECFDF5;color:#059669;">معتمد</span>',
'rejected' => '<span style="display:inline-block;padding:3px 10px;border-radius:10px;font-size:12px;font-weight:600;background:#FEF2F2;color:#DC2626;">مرفوض</span>',
default => $row['status']
} ?>
</td>
<td style="font-size:12px;color:#6B7280;"><?= e(substr($row['created_at'] ?? '', 0, 10)) ?></td>
<td style="white-space:nowrap;">
<?php if ($row['status'] === 'pending'): ?>
<form method="POST" action="/pricing/regulatory-discounts/applications/<?= (int) $row['id'] ?>/approve" style="display:inline;">
<?= csrf_field() ?>
<button type="submit" class="btn btn-sm" style="font-size:11px;padding:3px 8px;background:#ECFDF5;color:#059669;border:1px solid #A7F3D0;">اعتماد</button>
</form>
<button type="button" class="btn btn-sm" style="font-size:11px;padding:3px 8px;background:#FEF2F2;color:#DC2626;border:1px solid #FECACA;" onclick="rejectApp(<?= (int) $row['id'] ?>)">رفض</button>
<?php elseif ($row['status'] === 'rejected' && $row['rejection_reason']): ?>
<span title="<?= e($row['rejection_reason']) ?>" style="cursor:help;color:#DC2626;font-size:11px;">سبب الرفض</span>
<?php endif; ?>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<?php if ($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 ?>&status=<?= urlencode($statusFilter ?? '') ?>" 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="file-check" 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;">تظهر هنا طلبات تطبيق الخصم اللائحي عند تقديمها من شاشة العضويات.</p>
</div>
<?php endif; ?>
</div>
<!-- Rejection Modal -->
<div id="reject-modal" style="display:none;position:fixed;top:0;left:0;right:0;bottom:0;background:rgba(0,0,0,0.5);z-index:9999;align-items:center;justify-content:center;">
<div style="background:#fff;border-radius:12px;padding:25px;max-width:400px;width:90%;">
<h4 style="margin:0 0 15px;color:#DC2626;">رفض طلب الخصم</h4>
<form method="POST" id="reject-form">
<?= csrf_field() ?>
<div class="form-group" style="margin-bottom:15px;">
<label class="form-label">سبب الرفض (اختياري)</label>
<textarea name="rejection_reason" class="form-input" rows="3" placeholder="أدخل سبب الرفض..."></textarea>
</div>
<div style="display:flex;gap:10px;">
<button type="submit" class="btn" style="background:#DC2626;color:#fff;padding:8px 20px;">تأكيد الرفض</button>
<button type="button" class="btn btn-outline" onclick="closeRejectModal()">إلغاء</button>
</div>
</form>
</div>
</div>
<script>
document.addEventListener('DOMContentLoaded', function() { if (typeof lucide !== 'undefined') lucide.createIcons(); });
function rejectApp(id) {
document.getElementById('reject-form').action = '/pricing/regulatory-discounts/applications/' + id + '/reject';
document.getElementById('reject-modal').style.display = 'flex';
}
function closeRejectModal() {
document.getElementById('reject-modal').style.display = 'none';
}
</script>
<?php $__template->endSection(); ?>
<?php
$__template->layout('Layout.main');
$isEdit = $discount !== null;
$__template->section('title');
echo $isEdit ? 'تعديل خصم لائحي' : 'إضافة خصم لائحي';
$__template->endSection();
?>
<?php $__template->section('page_actions'); ?>
<a href="/pricing/regulatory-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'); ?>
<div class="card" style="max-width:900px;">
<div style="padding:15px 20px;border-bottom:1px solid #E5E7EB;">
<h3 style="margin:0;color:#0D7377;font-size:15px;">
<i data-lucide="scale" style="width:16px;height:16px;vertical-align:middle;margin-left:6px;"></i>
<?= $isEdit ? 'تعديل الخصم اللائحي' : 'إضافة خصم لائحي جديد' ?>
</h3>
</div>
<form method="POST" action="<?= $isEdit ? '/pricing/regulatory-discounts/' . (int) $discount['id'] : '/pricing/regulatory-discounts' ?>" style="padding:20px;">
<?= csrf_field() ?>
<!-- Basic Info -->
<div style="display:grid;grid-template-columns:1fr 1fr 1fr;gap:15px;margin-bottom:20px;">
<div class="form-group">
<label class="form-label">رقم المادة <span style="color:red;">*</span></label>
<input type="text" name="article_number" value="<?= e($discount['article_number'] ?? '') ?>" class="form-input" placeholder="مثال: 97" required>
</div>
<div class="form-group" style="grid-column:span 2;">
<label class="form-label">اسم الخصم بالعربية <span style="color:red;">*</span></label>
<input type="text" name="name_ar" value="<?= e($discount['name_ar'] ?? '') ?>" class="form-input" placeholder="مثال: خصم أعضاء فرع السادس لفرع شيراتون" required>
</div>
</div>
<div style="display:grid;grid-template-columns:1fr 1fr;gap:15px;margin-bottom:20px;">
<div class="form-group">
<label class="form-label">اسم الخصم بالإنجليزية</label>
<input type="text" name="name_en" value="<?= e($discount['name_en'] ?? '') ?>" class="form-input" placeholder="Optional">
</div>
<div class="form-group">
<label class="form-label">نوع الاستحقاق <span style="color:red;">*</span></label>
<select name="eligibility_type" id="eligibility_type" class="form-input" required>
<option value="cross_branch_member" <?= ($discount['eligibility_type'] ?? '') === 'cross_branch_member' ? 'selected' : '' ?>>عضو فرع آخر (مادة 97)</option>
<option value="government_employee" <?= ($discount['eligibility_type'] ?? '') === 'government_employee' ? 'selected' : '' ?>>موظف حكومي (مادة 98)</option>
<option value="ministry_youth_employee" <?= ($discount['eligibility_type'] ?? '') === 'ministry_youth_employee' ? 'selected' : '' ?>>موظف وزارة الشباب (مادة 98/99)</option>
<option value="board_of_trustees" <?= ($discount['eligibility_type'] ?? '') === 'board_of_trustees' ? 'selected' : '' ?>>عضو مجلس أمناء (مادة 100)</option>
<option value="club_employee" <?= ($discount['eligibility_type'] ?? '') === 'club_employee' ? 'selected' : '' ?>>موظف النادي (مادة 102)</option>
<option value="group_membership" <?= ($discount['eligibility_type'] ?? '') === 'group_membership' ? 'selected' : '' ?>>عضوية مجمعة (مادة 110)</option>
</select>
</div>
</div>
<!-- Discount Value -->
<div id="section-discount-value" style="display:grid;grid-template-columns:1fr 1fr 1fr;gap:15px;margin-bottom:20px;">
<div class="form-group">
<label class="form-label">نسبة الخصم (%) <span style="color:red;">*</span></label>
<input type="number" name="discount_percentage" step="0.01" min="0" max="100" value="<?= e($discount['discount_percentage'] ?? '0') ?>" class="form-input">
</div>
<div class="form-group">
<label class="form-label">الحد الأقصى للخصم (%)</label>
<input type="number" name="max_discount_percentage" step="0.01" min="0" max="100" value="<?= e($discount['max_discount_percentage'] ?? '') ?>" class="form-input" placeholder="اختياري — لمادة 102">
</div>
<div class="form-group">
<label class="form-label">يُطبَّق على</label>
<select name="applies_to" class="form-input">
<option value="membership_fee" <?= ($discount['applies_to'] ?? 'membership_fee') === 'membership_fee' ? 'selected' : '' ?>>رسوم العضوية</option>
<option value="subscription" <?= ($discount['applies_to'] ?? '') === 'subscription' ? 'selected' : '' ?>>الاشتراك</option>
<option value="all" <?= ($discount['applies_to'] ?? '') === 'all' ? 'selected' : '' ?>>الكل</option>
</select>
</div>
</div>
<!-- Branch Selection -->
<div id="section-branches" style="display:grid;grid-template-columns:1fr 1fr;gap:15px;margin-bottom:20px;">
<div class="form-group">
<label class="form-label">فرع المصدر (عضوية العضو الحالية)</label>
<select name="source_branch_id" class="form-input">
<option value="">— غير محدد —</option>
<?php foreach ($branches as $b): ?>
<option value="<?= (int) $b['id'] ?>" <?= ((int) ($discount['source_branch_id'] ?? 0)) === (int) $b['id'] ? 'selected' : '' ?>><?= e($b['name_ar']) ?></option>
<?php endforeach; ?>
</select>
</div>
<div class="form-group">
<label class="form-label">الفرع المستهدف (المراد الانضمام إليه)</label>
<select name="target_branch_id" class="form-input">
<option value="">— الكل —</option>
<?php foreach ($branches as $b): ?>
<option value="<?= (int) $b['id'] ?>" <?= ((int) ($discount['target_branch_id'] ?? 0)) === (int) $b['id'] ? 'selected' : '' ?>><?= e($b['name_ar']) ?></option>
<?php endforeach; ?>
</select>
</div>
</div>
<!-- Employee-specific -->
<div id="section-employee" style="display:grid;grid-template-columns:1fr;gap:15px;margin-bottom:20px;">
<div class="form-group">
<label class="form-label">الحد الأدنى لسنوات الخدمة</label>
<input type="number" name="min_service_years" min="0" value="<?= e($discount['min_service_years'] ?? '0') ?>" class="form-input" placeholder="مثال: 5 (لمادة 102)">
</div>
</div>
<!-- Installment -->
<div style="padding:15px;background:#F9FAFB;border-radius:8px;margin-bottom:20px;">
<label style="display:flex;align-items:center;gap:8px;cursor:pointer;margin-bottom:12px;">
<input type="checkbox" name="allows_installment" value="1" id="allows_installment" <?= ($discount['allows_installment'] ?? 0) ? 'checked' : '' ?>>
<span style="font-weight:600;color:#374151;">يسمح بالتقسيط</span>
</label>
<div id="section-installment" style="display:grid;grid-template-columns:1fr 1fr;gap:15px;<?= ($discount['allows_installment'] ?? 0) ? '' : 'display:none;' ?>">
<div class="form-group">
<label class="form-label">أقصى مدة تقسيط (سنوات)</label>
<input type="number" name="installment_max_years" min="1" max="10" value="<?= e($discount['installment_max_years'] ?? '2') ?>" class="form-input">
</div>
<div class="form-group">
<label class="form-label">نسبة الفائدة على التقسيط (%)</label>
<input type="number" name="installment_interest_rate" step="0.01" min="0" value="<?= e($discount['installment_interest_rate'] ?? '0') ?>" class="form-input" placeholder="0 = بدون فائدة">
</div>
</div>
</div>
<!-- Group Tiers (Article 110) -->
<div id="section-group-tiers" style="padding:15px;background:#EEF2FF;border-radius:8px;margin-bottom:20px;<?= ($discount['eligibility_type'] ?? '') === 'group_membership' ? '' : 'display:none;' ?>">
<h4 style="color:#4F46E5;margin:0 0 12px;font-size:14px;">
<i data-lucide="layers" style="width:14px;height:14px;vertical-align:middle;margin-left:4px;"></i>
شرائح الخصم حسب العدد (مادة 110)
</h4>
<div id="tiers-container">
<?php if (!empty($tiers)): ?>
<?php foreach ($tiers as $idx => $tier): ?>
<div class="tier-row" style="display:grid;grid-template-columns:1fr 1fr 1fr 40px;gap:10px;margin-bottom:8px;align-items:end;">
<div class="form-group" style="margin:0;">
<label class="form-label" style="font-size:11px;">من (عدد)</label>
<input type="number" name="tier_min[]" value="<?= (int) $tier['min_quantity'] ?>" class="form-input" min="1">
</div>
<div class="form-group" style="margin:0;">
<label class="form-label" style="font-size:11px;">إلى (عدد)</label>
<input type="number" name="tier_max[]" value="<?= $tier['max_quantity'] ? (int) $tier['max_quantity'] : '' ?>" class="form-input" placeholder="مفتوح">
</div>
<div class="form-group" style="margin:0;">
<label class="form-label" style="font-size:11px;">نسبة الخصم %</label>
<input type="number" name="tier_pct[]" value="<?= e($tier['discount_percentage']) ?>" step="0.01" class="form-input">
</div>
<button type="button" class="btn btn-sm" style="color:#DC2626;padding:6px;" onclick="this.closest('.tier-row').remove();"></button>
</div>
<?php endforeach; ?>
<?php else: ?>
<div class="tier-row" style="display:grid;grid-template-columns:1fr 1fr 1fr 40px;gap:10px;margin-bottom:8px;align-items:end;">
<div class="form-group" style="margin:0;">
<label class="form-label" style="font-size:11px;">من (عدد)</label>
<input type="number" name="tier_min[]" value="5" class="form-input" min="1">
</div>
<div class="form-group" style="margin:0;">
<label class="form-label" style="font-size:11px;">إلى (عدد)</label>
<input type="number" name="tier_max[]" value="10" class="form-input" placeholder="مفتوح">
</div>
<div class="form-group" style="margin:0;">
<label class="form-label" style="font-size:11px;">نسبة الخصم %</label>
<input type="number" name="tier_pct[]" value="3" step="0.01" class="form-input">
</div>
<button type="button" class="btn btn-sm" style="color:#DC2626;padding:6px;" onclick="this.closest('.tier-row').remove();"></button>
</div>
<div class="tier-row" style="display:grid;grid-template-columns:1fr 1fr 1fr 40px;gap:10px;margin-bottom:8px;align-items:end;">
<div class="form-group" style="margin:0;"><input type="number" name="tier_min[]" value="11" class="form-input" min="1"></div>
<div class="form-group" style="margin:0;"><input type="number" name="tier_max[]" value="20" class="form-input"></div>
<div class="form-group" style="margin:0;"><input type="number" name="tier_pct[]" value="7" step="0.01" class="form-input"></div>
<button type="button" class="btn btn-sm" style="color:#DC2626;padding:6px;" onclick="this.closest('.tier-row').remove();"></button>
</div>
<div class="tier-row" style="display:grid;grid-template-columns:1fr 1fr 1fr 40px;gap:10px;margin-bottom:8px;align-items:end;">
<div class="form-group" style="margin:0;"><input type="number" name="tier_min[]" value="21" class="form-input" min="1"></div>
<div class="form-group" style="margin:0;"><input type="number" name="tier_max[]" value="" class="form-input" placeholder="مفتوح"></div>
<div class="form-group" style="margin:0;"><input type="number" name="tier_pct[]" value="10" step="0.01" class="form-input"></div>
<button type="button" class="btn btn-sm" style="color:#DC2626;padding:6px;" onclick="this.closest('.tier-row').remove();"></button>
</div>
<?php endif; ?>
</div>
<button type="button" class="btn btn-sm btn-outline" style="margin-top:8px;" onclick="addTierRow()">
<i data-lucide="plus" style="width:12px;height:12px;vertical-align:middle;"></i> إضافة شريحة
</button>
</div>
<!-- Dates and Board Decision -->
<div style="display:grid;grid-template-columns:1fr 1fr 1fr 1fr;gap:15px;margin-bottom:20px;">
<div class="form-group">
<label class="form-label">سارٍ من</label>
<input type="date" name="effective_from" value="<?= e($discount['effective_from'] ?? '') ?>" class="form-input">
</div>
<div class="form-group">
<label class="form-label">سارٍ حتى</label>
<input type="date" name="effective_to" value="<?= e($discount['effective_to'] ?? '') ?>" class="form-input">
</div>
<div class="form-group">
<label class="form-label">رقم قرار مجلس الإدارة</label>
<input type="text" name="board_decision_number" value="<?= e($discount['board_decision_number'] ?? '') ?>" class="form-input">
</div>
<div class="form-group">
<label class="form-label">تاريخ القرار</label>
<input type="date" name="board_decision_date" value="<?= e($discount['board_decision_date'] ?? '') ?>" class="form-input">
</div>
</div>
<!-- Notes -->
<div class="form-group" style="margin-bottom:20px;">
<label class="form-label">ملاحظات</label>
<textarea name="notes" class="form-input" rows="3" placeholder="وصف أو ملاحظات إضافية..."><?= e($discount['notes'] ?? '') ?></textarea>
</div>
<!-- Description -->
<div class="form-group" style="margin-bottom:20px;">
<label class="form-label">وصف تفصيلي (يظهر عند التطبيق)</label>
<textarea name="description" class="form-input" rows="2"><?= e($discount['description'] ?? '') ?></textarea>
</div>
<?php if ($isEdit): ?>
<div class="form-group" style="margin-bottom:20px;">
<label class="form-label">الحالة</label>
<select name="is_active" class="form-input" style="max-width:200px;">
<option value="1" <?= ($discount['is_active'] ?? 1) ? 'selected' : '' ?>>فعال</option>
<option value="0" <?= !($discount['is_active'] ?? 1) ? 'selected' : '' ?>>معطل</option>
</select>
</div>
<?php endif; ?>
<div style="border-top:1px solid #E5E7EB;padding-top:15px;display:flex;gap:10px;">
<button type="submit" class="btn btn-primary">
<i data-lucide="check" style="width:15px;height:15px;vertical-align:middle;margin-left:4px;"></i>
<?= $isEdit ? 'حفظ التعديلات' : 'إضافة الخصم' ?>
</button>
<a href="/pricing/regulatory-discounts" class="btn btn-outline">إلغاء</a>
</div>
</form>
</div>
<script>
document.addEventListener('DOMContentLoaded', function() {
if (typeof lucide !== 'undefined') lucide.createIcons();
const typeSelect = document.getElementById('eligibility_type');
const sectionBranches = document.getElementById('section-branches');
const sectionEmployee = document.getElementById('section-employee');
const sectionGroupTiers = document.getElementById('section-group-tiers');
const sectionDiscountValue = document.getElementById('section-discount-value');
const installmentCheckbox = document.getElementById('allows_installment');
const sectionInstallment = document.getElementById('section-installment');
function updateVisibility() {
const type = typeSelect.value;
sectionBranches.style.display = (type === 'cross_branch_member') ? 'grid' : 'none';
sectionEmployee.style.display = (type === 'club_employee') ? 'grid' : 'none';
sectionGroupTiers.style.display = (type === 'group_membership') ? 'block' : 'none';
sectionDiscountValue.style.display = (type === 'group_membership') ? 'none' : 'grid';
}
typeSelect.addEventListener('change', updateVisibility);
updateVisibility();
installmentCheckbox.addEventListener('change', function() {
sectionInstallment.style.display = this.checked ? 'grid' : 'none';
});
});
function addTierRow() {
const container = document.getElementById('tiers-container');
const row = document.createElement('div');
row.className = 'tier-row';
row.style.cssText = 'display:grid;grid-template-columns:1fr 1fr 1fr 40px;gap:10px;margin-bottom:8px;align-items:end;';
row.innerHTML = `
<div class="form-group" style="margin:0;"><input type="number" name="tier_min[]" class="form-input" min="1" placeholder="من"></div>
<div class="form-group" style="margin:0;"><input type="number" name="tier_max[]" class="form-input" placeholder="إلى (مفتوح)"></div>
<div class="form-group" style="margin:0;"><input type="number" name="tier_pct[]" step="0.01" class="form-input" placeholder="نسبة %"></div>
<button type="button" class="btn btn-sm" style="color:#DC2626;padding:6px;" onclick="this.closest('.tier-row').remove();">✕</button>
`;
container.appendChild(row);
}
</script>
<?php $__template->endSection(); ?>
<?php $__template->layout('Layout.main'); ?>
<?php $__template->section('title'); ?>الخصومات اللائحية<?php $__template->endSection(); ?>
<?php $__template->section('page_actions'); ?>
<?php if (can('pricing.regulatory_discounts.create')): ?>
<a href="/pricing/regulatory-discounts/create" class="btn btn-primary">
<i data-lucide="plus" style="width:15px;height:15px;vertical-align:middle;margin-left:4px;"></i> إضافة خصم لائحي
</a>
<?php endif; ?>
<a href="/pricing/regulatory-discounts/applications" class="btn btn-outline">
<i data-lucide="file-check" 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'); ?>
<!-- Filters -->
<div class="card" style="margin-bottom:15px;padding:15px;">
<form method="GET" action="/pricing/regulatory-discounts" style="display:flex;gap:10px;align-items:end;flex-wrap:wrap;">
<div class="form-group" style="flex:1;min-width:200px;">
<label class="form-label">بحث</label>
<input type="text" name="q" value="<?= e($filters['search'] ?? '') ?>" class="form-input" placeholder="اسم الخصم أو رقم المادة...">
</div>
<div class="form-group" style="min-width:180px;">
<label class="form-label">نوع الاستحقاق</label>
<select name="eligibility_type" class="form-input">
<option value="">— الكل —</option>
<option value="cross_branch_member" <?= ($filters['eligibility_type'] ?? '') === 'cross_branch_member' ? 'selected' : '' ?>>عضو فرع آخر</option>
<option value="government_employee" <?= ($filters['eligibility_type'] ?? '') === 'government_employee' ? 'selected' : '' ?>>موظف حكومي</option>
<option value="ministry_youth_employee" <?= ($filters['eligibility_type'] ?? '') === 'ministry_youth_employee' ? 'selected' : '' ?>>موظف وزارة الشباب</option>
<option value="board_of_trustees" <?= ($filters['eligibility_type'] ?? '') === 'board_of_trustees' ? 'selected' : '' ?>>عضو مجلس أمناء</option>
<option value="club_employee" <?= ($filters['eligibility_type'] ?? '') === 'club_employee' ? 'selected' : '' ?>>موظف النادي</option>
<option value="group_membership" <?= ($filters['eligibility_type'] ?? '') === 'group_membership' ? 'selected' : '' ?>>عضوية مجمعة</option>
</select>
</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="scale" style="width:18px;height:18px;color:#0D7377;"></i>
<h3 style="margin:0;color:#0D7377;font-size:15px;">خصومات اللائحة (المواد 97-102، 110)</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>
<th></th>
</tr>
</thead>
<tbody>
<?php foreach ($rows as $row): ?>
<tr>
<td style="font-weight:700;color:#0D7377;">م. <?= e($row['article_number']) ?></td>
<td style="font-weight:600;"><?= e($row['name_ar']) ?></td>
<td style="font-size:12px;">
<?= match($row['eligibility_type']) {
'cross_branch_member' => '<span style="background:#EEF2FF;color:#4F46E5;padding:2px 8px;border-radius:4px;">عضو فرع آخر</span>',
'government_employee' => '<span style="background:#FEF3C7;color:#92400E;padding:2px 8px;border-radius:4px;">موظف حكومي</span>',
'ministry_youth_employee' => '<span style="background:#DBEAFE;color:#1E40AF;padding:2px 8px;border-radius:4px;">وزارة الشباب</span>',
'board_of_trustees' => '<span style="background:#FDE68A;color:#92400E;padding:2px 8px;border-radius:4px;">مجلس أمناء</span>',
'club_employee' => '<span style="background:#D1FAE5;color:#065F46;padding:2px 8px;border-radius:4px;">موظف النادي</span>',
'group_membership' => '<span style="background:#E0E7FF;color:#3730A3;padding:2px 8px;border-radius:4px;">عضوية مجمعة</span>',
default => $row['eligibility_type']
} ?>
</td>
<td style="font-weight:700;color:#0D7377;">
<?php if ($row['eligibility_type'] === 'group_membership'): ?>
متدرج
<?php else: ?>
<?= e($row['discount_percentage']) ?>%
<?php if ($row['max_discount_percentage']): ?>
<small style="color:#9CA3AF;">(أقصى <?= e($row['max_discount_percentage']) ?>%)</small>
<?php endif; ?>
<?php endif; ?>
</td>
<td style="font-size:12px;">
<?php
$targetBranch = null;
if ($row['target_branch_id']) {
foreach ($branches as $b) {
if ((int) $b['id'] === (int) $row['target_branch_id']) {
$targetBranch = $b['name_ar'];
break;
}
}
}
echo $targetBranch ? e($targetBranch) : '<span style="color:#9CA3AF;">الكل</span>';
?>
</td>
<td>
<?php if ($row['allows_installment']): ?>
<span style="color:#059669;font-weight:600;font-size:12px;">
<?= $row['installment_max_years'] ?> سنة
<?= bccomp($row['installment_interest_rate'], '0', 2) === 0 ? '(بدون فائدة)' : '(' . e($row['installment_interest_rate']) . '%)' ?>
</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="white-space:nowrap;">
<a href="/pricing/regulatory-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/regulatory-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'] ?? '') ?>&eligibility_type=<?= urlencode($filters['eligibility_type'] ?? '') ?>" 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="scale" 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;">أضف قواعد الخصم حسب اللائحة (مواد 97-102، 110).</p>
<a href="/pricing/regulatory-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(); ?>
...@@ -17,6 +17,7 @@ MenuRegistry::register('pricing', [ ...@@ -17,6 +17,7 @@ MenuRegistry::register('pricing', [
['label_ar' => 'لوحة التسعير', 'label_en' => 'Pricing Dashboard', 'route' => '/pricing', 'permission' => 'pricing.view', 'order' => 1], ['label_ar' => 'لوحة التسعير', 'label_en' => 'Pricing Dashboard', 'route' => '/pricing', 'permission' => 'pricing.view', 'order' => 1],
['label_ar' => 'عروض مجلس الإدارة', 'label_en' => 'Board Offers', 'route' => '/pricing/board-offers', 'permission' => 'pricing.board_offers.view', 'order' => 2], ['label_ar' => 'عروض مجلس الإدارة', 'label_en' => 'Board Offers', 'route' => '/pricing/board-offers', 'permission' => 'pricing.board_offers.view', 'order' => 2],
['label_ar' => 'الخصومات الخاصة', 'label_en' => 'Special Discounts', 'route' => '/pricing/special-discounts', 'permission' => 'pricing.special_discounts.view', 'order' => 3], ['label_ar' => 'الخصومات الخاصة', 'label_en' => 'Special Discounts', 'route' => '/pricing/special-discounts', 'permission' => 'pricing.special_discounts.view', 'order' => 3],
['label_ar' => 'الخصومات اللائحية', 'label_en' => 'Regulatory Discounts','route' => '/pricing/regulatory-discounts','permission' => 'pricing.regulatory_discounts.view', 'order' => 4],
], ],
]); ]);
...@@ -29,6 +30,10 @@ PermissionRegistry::register('pricing', [ ...@@ -29,6 +30,10 @@ PermissionRegistry::register('pricing', [
'pricing.special_discounts.view' => ['ar' => 'عرض الخصومات الخاصة', 'en' => 'View Special Discounts'], 'pricing.special_discounts.view' => ['ar' => 'عرض الخصومات الخاصة', 'en' => 'View Special Discounts'],
'pricing.special_discounts.create' => ['ar' => 'إنشاء خصم خاص', 'en' => 'Create Special Discount'], 'pricing.special_discounts.create' => ['ar' => 'إنشاء خصم خاص', 'en' => 'Create Special Discount'],
'pricing.special_discounts.edit' => ['ar' => 'تعديل الخصومات الخاصة', 'en' => 'Edit Special Discounts'], 'pricing.special_discounts.edit' => ['ar' => 'تعديل الخصومات الخاصة', 'en' => 'Edit Special Discounts'],
'pricing.regulatory_discounts.view' => ['ar' => 'عرض الخصومات اللائحية', 'en' => 'View Regulatory Discounts'],
'pricing.regulatory_discounts.create' => ['ar' => 'إنشاء خصم لائحي', 'en' => 'Create Regulatory Discount'],
'pricing.regulatory_discounts.edit' => ['ar' => 'تعديل الخصومات اللائحية', 'en' => 'Edit Regulatory Discounts'],
'pricing.regulatory_discounts.approve' => ['ar' => 'اعتماد طلبات الخصم اللائحي','en' => 'Approve Regulatory Discount Applications'],
]); ]);
// When a member is activated, check if their discount includes free subscription bonus // When a member is activated, check if their discount includes free subscription bonus
......
<?php
declare(strict_types=1);
return function (\App\Core\Database $db): void {
$db->raw("
CREATE TABLE IF NOT EXISTS `regulatory_discounts` (
`id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
`article_number` VARCHAR(20) NOT NULL COMMENT 'رقم المادة في اللائحة',
`name_ar` VARCHAR(300) NOT NULL,
`name_en` VARCHAR(300) NULL,
`description` TEXT NULL,
`discount_percentage` DECIMAL(5,2) NOT NULL DEFAULT 0.00,
`max_discount_percentage` DECIMAL(5,2) NULL COMMENT 'الحد الأقصى للخصم (لمادة 102)',
`eligibility_type` ENUM(
'cross_branch_member',
'government_employee',
'ministry_youth_employee',
'board_of_trustees',
'club_employee',
'group_membership'
) NOT NULL,
`source_branch_id` BIGINT UNSIGNED NULL COMMENT 'فرع العضو الحالي (لمادة 97)',
`target_branch_id` BIGINT UNSIGNED NULL COMMENT 'الفرع المستهدف بالخصم',
`min_service_years` INT UNSIGNED NULL COMMENT 'الحد الأدنى لسنوات الخدمة (لمادة 102)',
`allows_installment` TINYINT(1) NOT NULL DEFAULT 0,
`installment_max_years` INT UNSIGNED NULL COMMENT 'أقصى مدة تقسيط بالسنوات',
`installment_interest_rate` DECIMAL(5,2) NOT NULL DEFAULT 0.00 COMMENT 'نسبة الفائدة على التقسيط',
`applies_to` ENUM('membership_fee','subscription','all') NOT NULL DEFAULT 'membership_fee',
`is_active` TINYINT(1) NOT NULL DEFAULT 1,
`effective_from` DATE NULL,
`effective_to` DATE NULL,
`board_decision_number` VARCHAR(50) NULL,
`board_decision_date` DATE NULL,
`notes` TEXT NULL,
`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_regulatory_eligibility` (`eligibility_type`, `is_active`),
INDEX `idx_regulatory_target_branch` (`target_branch_id`, `is_active`),
INDEX `idx_regulatory_article` (`article_number`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
");
$db->raw("
CREATE TABLE IF NOT EXISTS `regulatory_discount_group_tiers` (
`id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
`regulatory_discount_id` BIGINT UNSIGNED NOT NULL,
`min_quantity` INT UNSIGNED NOT NULL,
`max_quantity` INT UNSIGNED NULL,
`discount_percentage` DECIMAL(5,2) NOT NULL,
`is_active` TINYINT(1) NOT NULL DEFAULT 1,
`created_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
INDEX `idx_group_tier_discount` (`regulatory_discount_id`),
CONSTRAINT `fk_group_tier_discount` FOREIGN KEY (`regulatory_discount_id`) REFERENCES `regulatory_discounts`(`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
");
$db->raw("
CREATE TABLE IF NOT EXISTS `regulatory_discount_applications` (
`id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
`regulatory_discount_id` BIGINT UNSIGNED NOT NULL,
`member_id` BIGINT UNSIGNED NULL,
`applicant_name` VARCHAR(200) NULL COMMENT 'الاسم (قبل إنشاء العضوية)',
`discount_percentage` DECIMAL(5,2) NOT NULL,
`original_amount` DECIMAL(15,2) NOT NULL,
`discount_amount` DECIMAL(15,2) NOT NULL,
`final_amount` DECIMAL(15,2) NOT NULL,
`status` ENUM('pending','approved','rejected','applied') NOT NULL DEFAULT 'pending',
`approved_by` BIGINT UNSIGNED NULL,
`approved_at` TIMESTAMP NULL,
`rejection_reason` TEXT NULL,
`verification_data` JSON NULL COMMENT 'بيانات التحقق (HR، فرع المصدر، إلخ)',
`installment_plan_id` BIGINT UNSIGNED NULL,
`notes` TEXT NULL,
`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,
INDEX `idx_rda_discount` (`regulatory_discount_id`),
INDEX `idx_rda_member` (`member_id`),
INDEX `idx_rda_status` (`status`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
");
};
<?php
declare(strict_types=1);
use App\Core\Database;
return function (Database $db): void {
// Branch IDs:
// 1 = شيراتون, 2 = السادس من أكتوبر, 3 = العاصمة الإدارية, 4 = المعادي
$discounts = [
// Article 97: Cross-branch 50% (6th Oct ↔ Sheraton)
[
'article_number' => '97',
'name_ar' => 'خصم أعضاء فرع السادس لفرع شيراتون',
'name_en' => 'Cross-branch: 6th Oct to Sheraton',
'description' => 'خصم 50% لأعضاء فرع السادس من أكتوبر عند الانضمام لفرع شيراتون (مادة 97)',
'discount_percentage' => '50.00',
'eligibility_type' => 'cross_branch_member',
'source_branch_id' => 2,
'target_branch_id' => 1,
'applies_to' => 'membership_fee',
],
[
'article_number' => '97',
'name_ar' => 'خصم أعضاء فرع شيراتون لفرع السادس',
'name_en' => 'Cross-branch: Sheraton to 6th Oct',
'description' => 'خصم 50% لأعضاء فرع شيراتون عند الانضمام لفرع السادس من أكتوبر (مادة 97)',
'discount_percentage' => '50.00',
'eligibility_type' => 'cross_branch_member',
'source_branch_id' => 1,
'target_branch_id' => 2,
'applies_to' => 'membership_fee',
],
// Article 97: Cross-branch 25% (either → Admin Capital)
[
'article_number' => '97',
'name_ar' => 'خصم أعضاء فرع شيراتون لفرع العاصمة الإدارية',
'name_en' => 'Cross-branch: Sheraton to Admin Capital',
'description' => 'خصم 25% لأعضاء فرع شيراتون عند الانضمام لفرع العاصمة الإدارية (مادة 97)',
'discount_percentage' => '25.00',
'eligibility_type' => 'cross_branch_member',
'source_branch_id' => 1,
'target_branch_id' => 3,
'applies_to' => 'membership_fee',
],
[
'article_number' => '97',
'name_ar' => 'خصم أعضاء فرع السادس لفرع العاصمة الإدارية',
'name_en' => 'Cross-branch: 6th Oct to Admin Capital',
'description' => 'خصم 25% لأعضاء فرع السادس من أكتوبر عند الانضمام لفرع العاصمة الإدارية (مادة 97)',
'discount_percentage' => '25.00',
'eligibility_type' => 'cross_branch_member',
'source_branch_id' => 2,
'target_branch_id' => 3,
'applies_to' => 'membership_fee',
],
// Article 98: Government employees 50% at Admin Capital
[
'article_number' => '98',
'name_ar' => 'خصم الموظفين الحكوميين — العاصمة الإدارية',
'name_en' => 'Gov employees — Admin Capital',
'description' => 'خصم 50% لموظفي الجهاز الإداري للدولة بفرع العاصمة الإدارية (مادة 98)',
'discount_percentage' => '50.00',
'eligibility_type' => 'government_employee',
'target_branch_id' => 3,
'applies_to' => 'membership_fee',
],
// Article 98: Ministry of Youth 62.5% at Admin Capital
[
'article_number' => '98',
'name_ar' => 'خصم موظفي وزارة الشباب — العاصمة الإدارية',
'name_en' => 'Ministry of Youth — Admin Capital',
'description' => 'خصم 62.5% لموظفي وزارة الشباب والرياضة بفرع العاصمة الإدارية (مادة 98)',
'discount_percentage' => '62.50',
'eligibility_type' => 'ministry_youth_employee',
'target_branch_id' => 3,
'applies_to' => 'membership_fee',
],
// Article 99: Ministry of Youth 25% at 6th Oct or Sheraton
[
'article_number' => '99',
'name_ar' => 'خصم موظفي وزارة الشباب — فرع شيراتون',
'name_en' => 'Ministry of Youth — Sheraton',
'description' => 'خصم 25% لموظفي وزارة الشباب والرياضة بفرع شيراتون (مادة 99)',
'discount_percentage' => '25.00',
'eligibility_type' => 'ministry_youth_employee',
'target_branch_id' => 1,
'applies_to' => 'membership_fee',
],
[
'article_number' => '99',
'name_ar' => 'خصم موظفي وزارة الشباب — فرع السادس',
'name_en' => 'Ministry of Youth — 6th Oct',
'description' => 'خصم 25% لموظفي وزارة الشباب والرياضة بفرع السادس من أكتوبر (مادة 99)',
'discount_percentage' => '25.00',
'eligibility_type' => 'ministry_youth_employee',
'target_branch_id' => 2,
'applies_to' => 'membership_fee',
],
// Article 100: Board of Trustees 50% + 2yr installment
[
'article_number' => '100',
'name_ar' => 'خصم أعضاء مجلس الأمناء',
'name_en' => 'Board of Trustees',
'description' => 'خصم 50% لأعضاء مجلس الأمناء مع تقسيط على سنتين بدون فوائد (مادة 100)',
'discount_percentage' => '50.00',
'eligibility_type' => 'board_of_trustees',
'target_branch_id' => null,
'allows_installment' => 1,
'installment_max_years' => 2,
'installment_interest_rate' => '0.00',
'applies_to' => 'membership_fee',
],
// Article 101: Ministry employees installment only (no discount)
[
'article_number' => '101',
'name_ar' => 'تقسيط موظفي الوزارة',
'name_en' => 'Ministry employees installment',
'description' => 'تقسيط رسوم العضوية على سنتين بدون فوائد لموظفي الوزارة (مادة 101) — بدون خصم',
'discount_percentage' => '0.00',
'eligibility_type' => 'ministry_youth_employee',
'target_branch_id' => null,
'allows_installment' => 1,
'installment_max_years' => 2,
'installment_interest_rate' => '0.00',
'applies_to' => 'membership_fee',
'notes' => 'مادة 101 — تقسيط فقط بدون خصم إضافي. يُطبق إذا كان الموظف لا يستحق خصم مواد 98/99.',
],
// Article 102: Club employees (5+ years) up to 15%
[
'article_number' => '102',
'name_ar' => 'خصم موظفي النادي (5 سنوات فأكثر)',
'name_en' => 'Club employees 5+ years',
'description' => 'خصم حتى 15% لموظفي النادي الذين أمضوا 5 سنوات فأكثر (مادة 102)',
'discount_percentage' => '15.00',
'max_discount_percentage' => '15.00',
'eligibility_type' => 'club_employee',
'target_branch_id' => null,
'min_service_years' => 5,
'applies_to' => 'membership_fee',
],
// Article 110: Group membership (tiered — tiers stored separately)
[
'article_number' => '110',
'name_ar' => 'خصم العضوية المجمعة',
'name_en' => 'Group membership discount',
'description' => 'خصم متدرج حسب عدد الأعضاء في المجموعة الواحدة (مادة 110)',
'discount_percentage' => '0.00',
'eligibility_type' => 'group_membership',
'target_branch_id' => null,
'applies_to' => 'membership_fee',
],
];
$now = date('Y-m-d H:i:s');
$groupDiscountId = null;
foreach ($discounts as $d) {
$row = [
'article_number' => $d['article_number'],
'name_ar' => $d['name_ar'],
'name_en' => $d['name_en'] ?? null,
'description' => $d['description'] ?? null,
'discount_percentage' => $d['discount_percentage'],
'max_discount_percentage' => $d['max_discount_percentage'] ?? null,
'eligibility_type' => $d['eligibility_type'],
'source_branch_id' => $d['source_branch_id'] ?? null,
'target_branch_id' => $d['target_branch_id'] ?? null,
'min_service_years' => $d['min_service_years'] ?? null,
'allows_installment' => $d['allows_installment'] ?? 0,
'installment_max_years' => $d['installment_max_years'] ?? null,
'installment_interest_rate' => $d['installment_interest_rate'] ?? '0.00',
'applies_to' => $d['applies_to'] ?? 'membership_fee',
'is_active' => 1,
'notes' => $d['notes'] ?? null,
'created_at' => $now,
'updated_at' => $now,
];
$id = $db->insert('regulatory_discounts', $row);
if ($d['eligibility_type'] === 'group_membership') {
$groupDiscountId = (int) $id;
}
}
// Article 110 tiers
if ($groupDiscountId) {
$tiers = [
['min_quantity' => 5, 'max_quantity' => 10, 'discount_percentage' => '3.00'],
['min_quantity' => 11, 'max_quantity' => 20, 'discount_percentage' => '7.00'],
['min_quantity' => 21, 'max_quantity' => null, 'discount_percentage' => '10.00'],
];
foreach ($tiers as $tier) {
$db->insert('regulatory_discount_group_tiers', [
'regulatory_discount_id' => $groupDiscountId,
'min_quantity' => $tier['min_quantity'],
'max_quantity' => $tier['max_quantity'],
'discount_percentage' => $tier['discount_percentage'],
'is_active' => 1,
]);
}
}
};
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