################################################################################
#                                                                              #
#   ██████╗ ██████╗ ██████╗ ███████╗██████╗  █████╗ ███████╗███████╗          #
#  ██╔════╝██╔═══██╗██╔══██╗██╔════╝██╔══██╗██╔══██╗██╔════╝██╔════╝          #
#  ██║     ██║   ██║██║  ██║█████╗  ██████╔╝███████║███████╗█████╗            #
#  ██║     ██║   ██║██║  ██║██╔══╝  ██╔══██╗██╔══██║╚════██║██╔══╝            #
#  ╚██████╗╚██████╔╝██████╔╝███████╗██████╔╝██║  ██║███████║███████╗          #
#   ╚═════╝ ╚═════╝ ╚═════╝ ╚══════╝╚═════╝ ╚═╝  ╚═╝╚══════╝╚══════╝          #
#                                                                              #
#          COMPLETE CODEBASE DUMP — EVERY FILE, EVERY LINE                     #
#                                                                              #
################################################################################

==============================================================================
 PROJECT CODEBASE — FULL SOURCE DUMP
==============================================================================

 Generated:       2026-03-17 21:52:02
 Source Dir:       /Users/mahmoudaglan/clublaravel
 Total Files:      494
 Total Lines:      41346
 Total Size:       1.4MB

 THIS FILE CONTAINS THE COMPLETE CODEBASE INCLUDING:
   • All source code files (every language found)
   • All configuration files (json, yaml, toml, xml, ini, etc.)
   • All environment files (.env, .env.local, .env.production, etc.)
   • All Docker files (Dockerfile, docker-compose.yml, .dockerignore)
   • All CI/CD configs (Jenkinsfile, GitHub Actions, etc.)
   • All build configs (webpack, vite, tsconfig, Makefile, etc.)
   • All package manifests (package.json, Cargo.toml, go.mod, etc.)
   • All lock files (package-lock.json, yarn.lock, etc.)
   • All documentation (README, CHANGELOG, LICENSE, etc.)
   • All scripts (shell, python, etc.)

 STRUCTURE OF THIS FILE:
   1. DIRECTORY TREE
   2. FILE MAP (indexed table of every file)
   3. FILE TYPE BREAKDOWN (stats by extension)
   4. SKIPPED FILES (binary/oversized — for transparency)
   5. COMPLETE FILE CONTENTS (every file printed in full)

==============================================================================


╔══════════════════════════════════════════════════════════════════════════════╗
║  SECTION 1: DIRECTORY TREE                                                  ║
╚══════════════════════════════════════════════════════════════════════════════╝

(For a visual tree: brew install tree)

Collected file paths:
  .env.example
  app/Actions/Subscription/ApplyLateFeesAction.php
  app/Actions/Subscription/BulkRenewSubscriptions.php
  app/Actions/Subscription/CalculateSubscriptionFees.php
  app/Actions/Subscription/RenewMemberSubscription.php
  app/Casts/ArabicDateCast.php
  app/Casts/MoneyCast.php
  app/Console/Commands/ApplySubscriptionLateFees.php
  app/Console/Commands/AutoExpireCards.php
  app/Console/Commands/CheckExpiringDocuments.php
  app/Console/Commands/CleanOldBackups.php
  app/Console/Commands/CompletePenaltiesCommand.php
  app/Console/Commands/CreateBackup.php
  app/Console/Commands/ExpireOverdueApplicationsCommand.php
  app/Console/Commands/ExpireSuspensionsCommand.php
  app/Console/Commands/GenerateAnnualSubscriptions.php
  app/Console/Commands/GenerateSubscriptions.php
  app/Console/Commands/MarkOverdueInstallments.php
  app/Console/Commands/MarkOverdueSubscriptions.php
  app/Console/Commands/MemberStatsCacheCommand.php
  app/Console/Commands/PurgeAuditLogs.php
  app/Console/Commands/ResetFiscalYearSequences.php
  app/Console/Commands/ScheduleAdmin.php
  app/Console/Commands/SendPaymentRemindersCommand.php
  app/Console/Kernel.php
  app/DTOs/FeeCalculationResult.php
  app/DTOs/MemberData.php
  app/DTOs/ReceiptData.php
  app/Enums/AuditAction.php
  app/Enums/BoardDecisionResult.php
  app/Enums/BoardDecisionType.php
  app/Enums/BoardOfferStatus.php
  app/Enums/BookingStatus.php
  app/Enums/CardStatus.php
  app/Enums/CashRegisterStatus.php
  app/Enums/DependentStatus.php
  app/Enums/FeeCategory.php
  app/Enums/Gender.php
  app/Enums/GenderRestriction.php
  app/Enums/InstallmentPlanStatus.php
  app/Enums/InstallmentStatus.php
  app/Enums/InterviewResult.php
  app/Enums/InterviewStatus.php
  app/Enums/MembershipStatus.php
  app/Enums/MembershipType.php
  app/Enums/NotificationChannel.php
  app/Enums/NotificationPriority.php
  app/Enums/PaymentMethod.php
  app/Enums/PaymentStatus.php
  app/Enums/PenaltyStatus.php
  app/Enums/ReceiptStatus.php
  app/Enums/SubscriptionStatus.php
  app/Enums/SuspensionStatus.php
  app/Enums/TransferStatus.php
  app/Enums/TransferType.php
  app/Enums/ViolationSeverity.php
  app/Enums/ViolationStatus.php
  app/Enums/WorkflowProgressStatus.php
  app/Exceptions/BusinessRuleException.php
  app/Exceptions/DependentAgeRestrictionException.php
  app/Exceptions/DependentLimitExceededException.php
  app/Exceptions/DuplicateNationalIdException.php
  app/Exceptions/InsufficientBalanceException.php
  app/Exceptions/InvalidStatusTransitionException.php
  app/Exceptions/MemberNotEligibleException.php
  app/Exceptions/SequenceGenerationException.php
  app/Exports/FinancialExport.php
  app/Exports/MembersExport.php
  app/Exports/SubscriptionsExport.php
  app/Exports/ViolationsExport.php
  app/Filament/Pages/BackupManagement.php
  app/Filament/Pages/CardPreview.php
  app/Filament/Pages/Dashboard.php
  app/Filament/Pages/DocumentChecklist.php
  app/Filament/Pages/Reports/AgingReport.php
  app/Filament/Pages/Reports/ChurnReport.php
  app/Filament/Pages/Reports/FinancialReport.php
  app/Filament/Pages/Reports/InstallmentReport.php
  app/Filament/Pages/Reports/MembershipReport.php
  app/Filament/Pages/Reports/SubscriptionReport.php
  app/Filament/Pages/Reports/TopPayingMembersReport.php
  app/Filament/Pages/Reports/ViolationsReport.php
  app/Filament/Pages/SystemSettings.php
  app/Filament/Resources/ActivityLogResource.php
  app/Filament/Resources/ActivityLogResource/Pages/ListActivityLogs.php
  app/Filament/Resources/ActivityLogResource/Pages/ViewActivityLog.php
  app/Filament/Resources/BoardOfferResource.php
  app/Filament/Resources/BoardOfferResource/Pages/CreateBoardOffer.php
  app/Filament/Resources/BoardOfferResource/Pages/EditBoardOffer.php
  app/Filament/Resources/BoardOfferResource/Pages/ListBoardOffers.php
  app/Filament/Resources/BoardOfferResource/Pages/ViewBoardOffer.php
  app/Filament/Resources/BoardOfferResource/RelationManagers/DecisionsRelationManager.php
  app/Filament/Resources/CashRegisterResource.php
  app/Filament/Resources/CashRegisterResource/Pages/CreateCashRegister.php
  app/Filament/Resources/CashRegisterResource/Pages/ListCashRegisters.php
  app/Filament/Resources/CashRegisterResource/Pages/ViewCashRegister.php
  app/Filament/Resources/CashRegisterResource/RelationManagers/TransactionsRelationManager.php
  app/Filament/Resources/DependentResource.php
  app/Filament/Resources/DependentResource/Pages/CreateDependent.php
  app/Filament/Resources/DependentResource/Pages/EditDependent.php
  app/Filament/Resources/DependentResource/Pages/ListDependents.php
  app/Filament/Resources/DependentResource/Pages/ViewDependent.php
  app/Filament/Resources/DependentResource/RelationManagers/ActivityLogRelationManager.php
  app/Filament/Resources/DependentResource/RelationManagers/CardsRelationManager.php
  app/Filament/Resources/DependentResource/RelationManagers/DocumentsRelationManager.php
  app/Filament/Resources/DocumentRequirementResource.php
  app/Filament/Resources/DocumentRequirementResource/Pages/CreateDocumentRequirement.php
  app/Filament/Resources/DocumentRequirementResource/Pages/EditDocumentRequirement.php
  app/Filament/Resources/DocumentRequirementResource/Pages/ListDocumentRequirements.php
  app/Filament/Resources/DocumentTypeResource.php
  app/Filament/Resources/DocumentTypeResource/Pages/CreateDocumentType.php
  app/Filament/Resources/DocumentTypeResource/Pages/EditDocumentType.php
  app/Filament/Resources/DocumentTypeResource/Pages/ListDocumentTypes.php
  app/Filament/Resources/FeeStructureResource.php
  app/Filament/Resources/FeeStructureResource/Pages/CreateFeeStructure.php
  app/Filament/Resources/FeeStructureResource/Pages/EditFeeStructure.php
  app/Filament/Resources/FeeStructureResource/Pages/ListFeeStructures.php
  app/Filament/Resources/InstallmentPlanResource.php
  app/Filament/Resources/InstallmentPlanResource/Pages/CreateInstallmentPlan.php
  app/Filament/Resources/InstallmentPlanResource/Pages/ListInstallmentPlans.php
  app/Filament/Resources/InstallmentPlanResource/Pages/ViewInstallmentPlan.php
  app/Filament/Resources/InstallmentPlanResource/RelationManagers/InstallmentsRelationManager.php
  app/Filament/Resources/MemberCardResource.php
  app/Filament/Resources/MemberCardResource/Pages/CreateMemberCard.php
  app/Filament/Resources/MemberCardResource/Pages/EditMemberCard.php
  app/Filament/Resources/MemberCardResource/Pages/ListMemberCards.php
  app/Filament/Resources/MemberCardResource/Pages/ViewMemberCard.php
  app/Filament/Resources/MemberDocumentResource.php
  app/Filament/Resources/MemberDocumentResource/Pages/CreateMemberDocument.php
  app/Filament/Resources/MemberDocumentResource/Pages/EditMemberDocument.php
  app/Filament/Resources/MemberDocumentResource/Pages/ListMemberDocuments.php
  app/Filament/Resources/MemberDocumentResource/Pages/ViewMemberDocument.php
  app/Filament/Resources/MemberResource.php
  app/Filament/Resources/MemberResource/Pages/CreateMember.php
  app/Filament/Resources/MemberResource/Pages/EditMember.php
  app/Filament/Resources/MemberResource/Pages/ListMembers.php
  app/Filament/Resources/MemberResource/Pages/MemberWorkflow.php
  app/Filament/Resources/MemberResource/Pages/ViewMember.php
  app/Filament/Resources/MemberResource/RelationManagers/ActivityLogRelationManager.php
  app/Filament/Resources/MemberResource/RelationManagers/CardsRelationManager.php
  app/Filament/Resources/MemberResource/RelationManagers/DependentsRelationManager.php
  app/Filament/Resources/MemberResource/RelationManagers/DocumentsRelationManager.php
  app/Filament/Resources/MemberResource/RelationManagers/NotesRelationManager.php
  app/Filament/Resources/MemberResource/RelationManagers/ReceiptsRelationManager.php
  app/Filament/Resources/MemberResource/RelationManagers/SubscriptionsRelationManager.php
  app/Filament/Resources/MemberResource/RelationManagers/ViolationsRelationManager.php
  app/Filament/Resources/MemberSuspensionResource.php
  app/Filament/Resources/MemberSuspensionResource/Pages/CreateMemberSuspension.php
  app/Filament/Resources/MemberSuspensionResource/Pages/ListMemberSuspensions.php
  app/Filament/Resources/MemberSuspensionResource/Pages/ViewMemberSuspension.php
  app/Filament/Resources/NotificationResource.php
  app/Filament/Resources/NotificationResource/Pages/CreateNotification.php
  app/Filament/Resources/NotificationResource/Pages/ListNotifications.php
  app/Filament/Resources/NotificationResource/Pages/ViewNotification.php
  app/Filament/Resources/PenaltyResource.php
  app/Filament/Resources/PenaltyResource/Pages/CreatePenalty.php
  app/Filament/Resources/PenaltyResource/Pages/ListPenalties.php
  app/Filament/Resources/PenaltyResource/Pages/ViewPenalty.php
  app/Filament/Resources/PenaltyTypeResource.php
  app/Filament/Resources/PenaltyTypeResource/Pages/CreatePenaltyType.php
  app/Filament/Resources/PenaltyTypeResource/Pages/EditPenaltyType.php
  app/Filament/Resources/PenaltyTypeResource/Pages/ListPenaltyTypes.php
  app/Filament/Resources/ReceiptResource.php
  app/Filament/Resources/ReceiptResource/Pages/CreateReceipt.php
  app/Filament/Resources/ReceiptResource/Pages/ListReceipts.php
  app/Filament/Resources/ReceiptResource/Pages/ViewReceipt.php
  app/Filament/Resources/ReceiptResource/RelationManagers/ReceiptItemsRelationManager.php
  app/Filament/Resources/SequenceResource.php
  app/Filament/Resources/SequenceResource/Pages/EditSequence.php
  app/Filament/Resources/SequenceResource/Pages/ListSequences.php
  app/Filament/Resources/SubscriptionPeriodResource.php
  app/Filament/Resources/SubscriptionPeriodResource/Pages/CreateSubscriptionPeriod.php
  app/Filament/Resources/SubscriptionPeriodResource/Pages/EditSubscriptionPeriod.php
  app/Filament/Resources/SubscriptionPeriodResource/Pages/ListSubscriptionPeriods.php
  app/Filament/Resources/SubscriptionPeriodResource/Pages/ViewSubscriptionPeriod.php
  app/Filament/Resources/SubscriptionPeriodResource/RelationManagers/SubscriptionsRelationManager.php
  app/Filament/Resources/SubscriptionResource.php
  app/Filament/Resources/SubscriptionResource/Pages/CreateSubscription.php
  app/Filament/Resources/SubscriptionResource/Pages/EditSubscription.php
  app/Filament/Resources/SubscriptionResource/Pages/ListSubscriptions.php
  app/Filament/Resources/SubscriptionResource/Pages/ViewSubscription.php
  app/Filament/Resources/SubscriptionResource/RelationManagers/PaymentsRelationManager.php
  app/Filament/Resources/SubscriptionResource/RelationManagers/SubscriptionReceiptsRelationManager.php
  app/Filament/Resources/UserResource.php
  app/Filament/Resources/UserResource/Pages/CreateUser.php
  app/Filament/Resources/UserResource/Pages/EditUser.php
  app/Filament/Resources/UserResource/Pages/ListUsers.php
  app/Filament/Resources/UserResource/Pages/ViewUser.php
  app/Filament/Resources/UserResource/RelationManagers/ActivityLogRelationManager.php
  app/Filament/Resources/ViolationResource.php
  app/Filament/Resources/ViolationResource/Pages/CreateViolation.php
  app/Filament/Resources/ViolationResource/Pages/EditViolation.php
  app/Filament/Resources/ViolationResource/Pages/ListViolations.php
  app/Filament/Resources/ViolationResource/Pages/ViewViolation.php
  app/Filament/Resources/ViolationResource/RelationManagers/DocumentsRelationManager.php
  app/Filament/Resources/ViolationResource/RelationManagers/PenaltiesRelationManager.php
  app/Filament/Resources/ViolationTypeResource.php
  app/Filament/Resources/ViolationTypeResource/Pages/CreateViolationType.php
  app/Filament/Resources/ViolationTypeResource/Pages/EditViolationType.php
  app/Filament/Resources/ViolationTypeResource/Pages/ListViolationTypes.php
  app/Filament/Widgets/AdminStatsWidget.php
  app/Filament/Widgets/BoardOffersChartWidget.php
  app/Filament/Widgets/CardStatsWidget.php
  app/Filament/Widgets/CollectionByTypeWidget.php
  app/Filament/Widgets/DailyReceiptsSummaryWidget.php
  app/Filament/Widgets/DependentRelationshipChartWidget.php
  app/Filament/Widgets/DependentStatsWidget.php
  app/Filament/Widgets/DisciplinaryOverviewWidget.php
  app/Filament/Widgets/DocumentComplianceWidget.php
  app/Filament/Widgets/FinancialChartWidget.php
  app/Filament/Widgets/MemberStatsOverview.php
  app/Filament/Widgets/MemberWorkflowFunnel.php
  app/Filament/Widgets/MembershipGrowthChart.php
  app/Filament/Widgets/MembershipStatusChart.php
  app/Filament/Widgets/OverdueInstallmentsWidget.php
  app/Filament/Widgets/PendingActionsWidget.php
  app/Filament/Widgets/PendingDependentsWidget.php
  app/Filament/Widgets/RecentActivityWidget.php
  app/Filament/Widgets/RecentMembersWidget.php
  app/Filament/Widgets/RecentViolationsWidget.php
  app/Filament/Widgets/RevenueByCategoryChart.php
  app/Filament/Widgets/RevenueChart.php
  app/Filament/Widgets/StatsOverviewWidget.php
  app/Filament/Widgets/SubscriptionOverviewWidget.php
  app/Filament/Widgets/SubscriptionStatusChart.php
  app/Filament/Widgets/UpcomingExpirationsWidget.php
  app/Filament/Widgets/ViolationsOverviewWidget.php
  app/Helpers/DateHelper.php
  app/Helpers/MoneyHelper.php
  app/Helpers/NationalIdHelper.php
  app/Helpers/SettingsHelper.php
  app/Http/Controllers/Api/V1/CardVerificationController.php
  app/Http/Controllers/Api/V1/MemberLookupController.php
  app/Http/Controllers/CardController.php
  app/Http/Controllers/MemberPrintController.php
  app/Http/Controllers/ReceiptController.php
  app/Http/Middleware/CheckUserActive.php
  app/Http/Middleware/TrackLastLogin.php
  app/Imports/MembersImport.php
  app/Models/BackupRecord.php
  app/Models/BoardDecision.php
  app/Models/BoardOffer.php
  app/Models/Booking.php
  app/Models/Card.php
  app/Models/CardType.php
  app/Models/CashRegister.php
  app/Models/CashRegisterTransaction.php
  app/Models/Dependent.php
  app/Models/Document.php
  app/Models/DocumentType.php
  app/Models/EducationalQualification.php
  app/Models/ExitReason.php
  app/Models/Facility.php
  app/Models/FeeItem.php
  app/Models/FeeSchedule.php
  app/Models/FeeStructure.php
  app/Models/FiscalYear.php
  app/Models/Governorate.php
  app/Models/ImportJob.php
  app/Models/Installment.php
  app/Models/InstallmentPlan.php
  app/Models/Interview.php
  app/Models/MaritalStatus.php
  app/Models/Member.php
  app/Models/MemberSuspension.php
  app/Models/MembershipStatus.php
  app/Models/MembershipType.php
  app/Models/Nationality.php
  app/Models/Notification.php
  app/Models/NotificationTemplate.php
  app/Models/NumberingSequence.php
  app/Models/Penalty.php
  app/Models/PenaltyType.php
  app/Models/Receipt.php
  app/Models/ReceiptItem.php
  app/Models/RelationshipType.php
  app/Models/Religion.php
  app/Models/Sequence.php
  app/Models/Subscription.php
  app/Models/SubscriptionPayment.php
  app/Models/SubscriptionPeriod.php
  app/Models/SuspensionReason.php
  app/Models/SystemSetting.php
  app/Models/Traits/Archivable.php
  app/Models/Traits/HasActiveScope.php
  app/Models/Traits/HasAuditTrail.php
  app/Models/Traits/HasBilingualName.php
  app/Models/Traits/HasDisplayOrder.php
  app/Models/Traits/HasFiscalYear.php
  app/Models/Traits/HasMemberScope.php
  app/Models/Traits/HasNumberingSequence.php
  app/Models/Transfer.php
  app/Models/User.php
  app/Models/Violation.php
  app/Models/ViolationDocument.php
  app/Models/ViolationType.php
  app/Models/WorkflowProgress.php
  app/Models/WorkflowStage.php
  app/Observers/BoardOfferObserver.php
  app/Observers/CashRegisterObserver.php
  app/Observers/DependentObserver.php
  app/Observers/MemberCardObserver.php
  app/Observers/MemberDocumentObserver.php
  app/Observers/MemberObserver.php
  app/Observers/MemberSuspensionObserver.php
  app/Observers/PenaltyObserver.php
  app/Observers/ReceiptObserver.php
  app/Observers/SubscriptionObserver.php
  app/Observers/ViolationObserver.php
  app/Policies/BoardDecisionPolicy.php
  app/Policies/BoardOfferPolicy.php
  app/Policies/CardPolicy.php
  app/Policies/CashRegisterPolicy.php
  app/Policies/DependentPolicy.php
  app/Policies/DocumentRequirementPolicy.php
  app/Policies/DocumentTypePolicy.php
  app/Policies/MemberCardPolicy.php
  app/Policies/MemberDocumentPolicy.php
  app/Policies/MemberPolicy.php
  app/Policies/MemberSuspensionPolicy.php
  app/Policies/NotificationPolicy.php
  app/Policies/PenaltyPolicy.php
  app/Policies/PenaltyTypePolicy.php
  app/Policies/ReceiptPolicy.php
  app/Policies/SequencePolicy.php
  app/Policies/SubscriptionPeriodPolicy.php
  app/Policies/SubscriptionPolicy.php
  app/Policies/SystemSettingPolicy.php
  app/Policies/TransferPolicy.php
  app/Policies/UserPolicy.php
  app/Policies/ViolationPolicy.php
  app/Policies/ViolationTypePolicy.php
  app/Providers/AdminServiceProvider.php
  app/Providers/AppServiceProvider.php
  app/Providers/AuthServiceProvider.php
  app/Providers/CardsAndDocumentsServiceProvider.php
  app/Providers/DisciplinaryServiceProvider.php
  app/Providers/EventServiceProvider.php
  app/Providers/Filament/AdminPanelProvider.php
  app/Providers/FinancialServiceProvider.php
  app/Providers/MemberServiceProvider.php
  app/Providers/Phase5ServiceProvider.php
  app/Providers/SubscriptionServiceProvider.php
  app/Services/Admin/AuditService.php
  app/Services/Admin/BackupService.php
  app/Services/Admin/BoardService.php
  app/Services/Admin/CashRegisterService.php
  app/Services/Admin/NotificationDispatchService.php
  app/Services/Admin/SequenceService.php
  app/Services/Admin/SettingsService.php
  app/Services/Cards/CardService.php
  app/Services/Disciplinary/PenaltyService.php
  app/Services/Disciplinary/SuspensionService.php
  app/Services/Disciplinary/ViolationService.php
  app/Services/Documents/DocumentService.php
  app/Services/Financial/CashRegisterService.php
  app/Services/Financial/FeeCalculationService.php
  app/Services/Financial/InstallmentService.php
  app/Services/Financial/NumberingService.php
  app/Services/Financial/ReceiptService.php
  app/Services/Financial/SequenceService.php
  app/Services/Financial/SubscriptionService.php
  app/Services/Membership/DependentService.php
  app/Services/Membership/MemberNumberService.php
  app/Services/Membership/MembershipService.php
  app/Services/Membership/MembershipWorkflowService.php
  app/Services/Notifications/NotificationService.php
  app/Services/Reporting/DashboardStatisticsService.php
  app/Services/Reporting/FinancialReportService.php
  app/Services/Reporting/MembershipReportService.php
  app/Services/Reporting/ReportService.php
  app/Services/Reporting/SubscriptionReportService.php
  app/Services/Reporting/ViolationReportService.php
  app/Services/Subscription/RenewalService.php
  app/Services/Subscription/SubscriptionCalculatorService.php
  app/Services/Subscription/SubscriptionService.php
  bootstrap/providers.php
  code.txt
  code_10.txt
  code_11.txt
  code_12.txt
  code_13.txt
  code_2.txt
  code_3.txt
  code_4.txt
  code_5.txt
  code_6.txt
  code_7.txt
  code_8.txt
  code_9.txt
  codecoll.sh
  composer.json
  config/club.php
  database/factories/CardFactory.php
  database/factories/DependentFactory.php
  database/factories/MemberFactory.php
  database/factories/ReceiptFactory.php
  database/factories/SubscriptionFactory.php
  database/migrations/2024_01_01_000001_create_membership_types_table.php
  database/migrations/2024_01_01_000002_create_membership_statuses_table.php
  database/migrations/2024_01_01_000003_create_nationalities_table.php
  database/migrations/2024_01_01_000004_create_religions_table.php
  database/migrations/2024_01_01_000005_create_marital_statuses_table.php
  database/migrations/2024_01_01_000006_create_governorates_table.php
  database/migrations/2024_01_01_000007_create_educational_qualifications_table.php
  database/migrations/2024_01_01_000008_create_document_types_table.php
  database/migrations/2024_01_01_000010_create_members_table.php
  database/migrations/2024_01_01_000011_create_documents_table.php
  database/migrations/2024_01_01_000012_create_cards_table.php
  database/migrations/2024_01_01_000013_create_receipts_table.php
  database/migrations/2024_01_01_000014_create_receipt_items_table.php
  database/migrations/2024_01_01_000015_create_fee_structures_table.php
  database/migrations/2024_01_01_000016_create_workflow_stages_table.php
  database/migrations/2024_01_01_000017_create_interviews_table.php
  database/migrations/2024_01_01_000018_create_transfers_table.php
  database/migrations/2024_01_01_000019_create_installment_plans_table.php
  database/migrations/2024_01_01_000020_create_installments_table.php
  database/migrations/2024_01_01_000021_create_board_offers_table.php
  database/migrations/2024_01_01_000022_create_bookings_table.php
  database/migrations/2024_01_01_000023_create_facilities_table.php
  database/migrations/2024_01_01_000024_create_payment_methods_table.php
  database/migrations/2024_01_01_000025_create_cash_registers_table.php
  database/migrations/2024_01_01_040001_create_subscription_periods_table.php
  database/migrations/2024_01_01_040002_create_subscriptions_table.php
  database/migrations/2024_01_01_040003_create_subscription_payments_table.php
  database/migrations/2024_01_01_200001_create_violation_types_table.php
  database/migrations/2024_01_01_200002_create_penalty_types_table.php
  database/migrations/2024_01_01_200003_create_violations_table.php
  database/migrations/2024_01_01_200004_create_penalties_table.php
  database/migrations/2024_01_01_200005_create_member_suspensions_table.php
  database/migrations/2024_01_01_200006_create_violation_documents_table.php
  database/migrations/2024_01_15_000001_create_cash_register_transactions_table.php
  database/migrations/2024_01_15_000002_create_club_notifications_table.php
  database/migrations/2024_01_15_000002_create_receipt_items_table.php
  database/migrations/2024_01_15_000003_add_admin_fields_to_users_table.php
  database/seeders/DatabaseSeeder.php
  database/seeders/DocumentTypeSeeder.php
  database/seeders/EducationalQualificationSeeder.php
  database/seeders/FeeStructureSeeder.php
  database/seeders/GovernorateSeeder.php
  database/seeders/MaritalStatusSeeder.php
  database/seeders/MembershipStatusSeeder.php
  database/seeders/MembershipTypeSeeder.php
  database/seeders/NationalitySeeder.php
  database/seeders/PaymentMethodSeeder.php
  database/seeders/PenaltyTypeSeeder.php
  database/seeders/ReligionSeeder.php
  database/seeders/RolesAndPermissionsSeeder.php
  database/seeders/SystemSettingsSeeder.php
  database/seeders/ViolationTypeSeeder.php
  postcss.config.js
  resources/css/app.css
  resources/css/filament/admin/theme.css
  resources/js/app.js
  resources/views/filament/pages/backup-management.blade.php
  resources/views/filament/pages/card-preview.blade.php
  resources/views/filament/pages/document-checklist.blade.php
  resources/views/filament/pages/reports/aging-report.blade.php
  resources/views/filament/pages/reports/churn-report.blade.php
  resources/views/filament/pages/reports/financial-report.blade.php
  resources/views/filament/pages/reports/installment-report.blade.php
  resources/views/filament/pages/reports/membership-report.blade.php
  resources/views/filament/pages/reports/subscription-report.blade.php
  resources/views/filament/pages/reports/top-paying-members.blade.php
  resources/views/filament/pages/reports/violations-report.blade.php
  resources/views/filament/pages/system-settings.blade.php
  resources/views/filament/resources/member-resource/pages/member-workflow.blade.php
  resources/views/filament/widgets/recent-activity-widget.blade.php
  resources/views/pdf/receipt.blade.php
  resources/views/prints/member-profile.blade.php
  resources/views/reports/aging.blade.php
  resources/views/reports/churn.blade.php
  resources/views/reports/financial.blade.php
  resources/views/reports/installments.blade.php
  resources/views/reports/membership.blade.php
  resources/views/reports/subscriptions.blade.php
  resources/views/reports/top-paying-members.blade.php
  resources/views/reports/violations.blade.php
  routes/api.php
  routes/console.php
  routes/member-routes.php
  routes/web.php
  scripts/cleanup-root-junk.sh
  tailwind.config.js
  tests/Feature/Controllers/CardControllerTest.php
  tests/Feature/Controllers/ReceiptControllerTest.php
  tests/Feature/Imports/MembersImportTest.php
  tests/Pest.php
  tests/TestCase.php
  tests/Unit/Helpers/MoneyHelperTest.php
  tests/Unit/Helpers/NationalIdHelperTest.php
  tests/Unit/Models/MemberTest.php
  tests/Unit/Services/FeeCalculationServiceTest.php
  vite.config.js


╔══════════════════════════════════════════════════════════════════════════════╗
║  SECTION 2: FILE MAP — INDEXED LIST OF ALL 494 FILES
╚══════════════════════════════════════════════════════════════════════════════╝

 Each file below appears in SECTION 5 with full contents.
 Use [###] index to jump to any file.

 INDEX   LINES    SIZE      FILE PATH
 -----   -----    ------    ----------------------------------------------
 [001]  52       1KB       .env.example
 [002]  20       466B      app/Actions/Subscription/ApplyLateFeesAction.php
 [003]  20       469B      app/Actions/Subscription/BulkRenewSubscriptions.php
 [004]  21       566B      app/Actions/Subscription/CalculateSubscriptionFees.php
 [005]  33       870B      app/Actions/Subscription/RenewMemberSubscription.php
 [006]  27       683B      app/Casts/ArabicDateCast.php
 [007]  26       619B      app/Casts/MoneyCast.php
 [008]  45       1KB       app/Console/Commands/ApplySubscriptionLateFees.php
 [009]  21       555B      app/Console/Commands/AutoExpireCards.php
 [010]  35       1KB       app/Console/Commands/CheckExpiringDocuments.php
 [011]  25       669B      app/Console/Commands/CleanOldBackups.php
 [012]  23       580B      app/Console/Commands/CompletePenaltiesCommand.php
 [013]  29       804B      app/Console/Commands/CreateBackup.php
 [014]  27       856B      app/Console/Commands/ExpireOverdueApplicationsCommand.php
 [015]  23       576B      app/Console/Commands/ExpireSuspensionsCommand.php
 [016]  31       1005B     app/Console/Commands/GenerateAnnualSubscriptions.php
 [017]  42       1KB       app/Console/Commands/GenerateSubscriptions.php
 [018]  19       542B      app/Console/Commands/MarkOverdueInstallments.php
 [019]  24       718B      app/Console/Commands/MarkOverdueSubscriptions.php
 [020]  49       2KB       app/Console/Commands/MemberStatsCacheCommand.php
 [021]  27       805B      app/Console/Commands/PurgeAuditLogs.php
 [022]  26       847B      app/Console/Commands/ResetFiscalYearSequences.php
 [023]  6        278B      app/Console/Commands/ScheduleAdmin.php
 [024]  58       2KB       app/Console/Commands/SendPaymentRemindersCommand.php
 [025]  34       1KB       app/Console/Kernel.php
 [026]  24       610B      app/DTOs/FeeCalculationResult.php
 [027]  57       2KB       app/DTOs/MemberData.php
 [028]  39       1KB       app/DTOs/ReceiptData.php
 [029]  107      3KB       app/Enums/AuditAction.php
 [030]  62       1KB       app/Enums/BoardDecisionResult.php
 [031]  97       3KB       app/Enums/BoardDecisionType.php
 [032]  67       1KB       app/Enums/BoardOfferStatus.php
 [033]  67       1KB       app/Enums/BookingStatus.php
 [034]  87       2KB       app/Enums/CardStatus.php
 [035]  52       1KB       app/Enums/CashRegisterStatus.php
 [036]  67       1KB       app/Enums/DependentStatus.php
 [037]  115      4KB       app/Enums/FeeCategory.php
 [038]  47       1005B     app/Enums/Gender.php
 [039]  52       1KB       app/Enums/GenderRestriction.php
 [040]  62       1KB       app/Enums/InstallmentPlanStatus.php
 [041]  67       1KB       app/Enums/InstallmentStatus.php
 [042]  57       1KB       app/Enums/InterviewResult.php
 [043]  67       1KB       app/Enums/InterviewStatus.php
 [044]  155      5KB       app/Enums/MembershipStatus.php
 [045]  67       1KB       app/Enums/MembershipType.php
 [046]  57       1KB       app/Enums/NotificationChannel.php
 [047]  57       1KB       app/Enums/NotificationPriority.php
 [048]  40       1KB       app/Enums/PaymentMethod.php
 [049]  62       1KB       app/Enums/PaymentStatus.php
 [050]  82       2KB       app/Enums/PenaltyStatus.php
 [051]  77       2KB       app/Enums/ReceiptStatus.php
 [052]  80       2KB       app/Enums/SubscriptionStatus.php
 [053]  62       1KB       app/Enums/SuspensionStatus.php
 [054]  72       2KB       app/Enums/TransferStatus.php
 [055]  57       1KB       app/Enums/TransferType.php
 [056]  57       1KB       app/Enums/ViolationSeverity.php
 [057]  77       2KB       app/Enums/ViolationStatus.php
 [058]  67       1KB       app/Enums/WorkflowProgressStatus.php
 [059]  20       438B      app/Exceptions/BusinessRuleException.php
 [060]  10       156B      app/Exceptions/DependentAgeRestrictionException.php
 [061]  10       155B      app/Exceptions/DependentLimitExceededException.php
 [062]  10       152B      app/Exceptions/DuplicateNationalIdException.php
 [063]  10       301B      app/Exceptions/InsufficientBalanceException.php
 [064]  11       157B      app/Exceptions/InvalidStatusTransitionException.php
 [065]  17       491B      app/Exceptions/MemberNotEligibleException.php
 [066]  15       406B      app/Exceptions/SequenceGenerationException.php
 [067]  81       2KB       app/Exports/FinancialExport.php
 [068]  82       2KB       app/Exports/MembersExport.php
 [069]  71       2KB       app/Exports/SubscriptionsExport.php
 [070]  75       2KB       app/Exports/ViolationsExport.php
 [071]  122      4KB       app/Filament/Pages/BackupManagement.php
 [072]  91       2KB       app/Filament/Pages/CardPreview.php
 [073]  52       1KB       app/Filament/Pages/Dashboard.php
 [074]  139      4KB       app/Filament/Pages/DocumentChecklist.php
 [075]  125      4KB       app/Filament/Pages/Reports/AgingReport.php
 [076]  61       1KB       app/Filament/Pages/Reports/ChurnReport.php
 [077]  242      8KB       app/Filament/Pages/Reports/FinancialReport.php
 [078]  160      5KB       app/Filament/Pages/Reports/InstallmentReport.php
 [079]  249      8KB       app/Filament/Pages/Reports/MembershipReport.php
 [080]  199      6KB       app/Filament/Pages/Reports/SubscriptionReport.php
 [081]  100      3KB       app/Filament/Pages/Reports/TopPayingMembersReport.php
 [082]  223      7KB       app/Filament/Pages/Reports/ViolationsReport.php
 [083]  195      5KB       app/Filament/Pages/SystemSettings.php
 [084]  186      7KB       app/Filament/Resources/ActivityLogResource.php
 [085]  37       1KB       app/Filament/Resources/ActivityLogResource/Pages/ListActivityLogs.php
 [086]  10       270B      app/Filament/Resources/ActivityLogResource/Pages/ViewActivityLog.php
 [087]  455      19KB      app/Filament/Resources/BoardOfferResource.php
 [088]  27       842B      app/Filament/Resources/BoardOfferResource/Pages/CreateBoardOffer.php
 [089]  25       669B      app/Filament/Resources/BoardOfferResource/Pages/EditBoardOffer.php
 [090]  46       1KB       app/Filament/Resources/BoardOfferResource/Pages/ListBoardOffers.php
 [091]  19       484B      app/Filament/Resources/BoardOfferResource/Pages/ViewBoardOffer.php
 [092]  130      4KB       app/Filament/Resources/BoardOfferResource/RelationManagers/DecisionsRelationManager.php
 [093]  345      14KB      app/Filament/Resources/CashRegisterResource.php
 [094]  26       727B      app/Filament/Resources/CashRegisterResource/Pages/CreateCashRegister.php
 [095]  41       1KB       app/Filament/Resources/CashRegisterResource/Pages/ListCashRegisters.php
 [096]  10       274B      app/Filament/Resources/CashRegisterResource/Pages/ViewCashRegister.php
 [097]  74       2KB       app/Filament/Resources/CashRegisterResource/RelationManagers/TransactionsRelationManager.php
 [098]  920      46KB      app/Filament/Resources/DependentResource.php
 [099]  54       1KB       app/Filament/Resources/DependentResource/Pages/CreateDependent.php
 [100]  41       992B      app/Filament/Resources/DependentResource/Pages/EditDependent.php
 [101]  73       2KB       app/Filament/Resources/DependentResource/Pages/ListDependents.php
 [102]  152      6KB       app/Filament/Resources/DependentResource/Pages/ViewDependent.php
 [103]  90       3KB       app/Filament/Resources/DependentResource/RelationManagers/ActivityLogRelationManager.php
 [104]  80       2KB       app/Filament/Resources/DependentResource/RelationManagers/CardsRelationManager.php
 [105]  127      4KB       app/Filament/Resources/DependentResource/RelationManagers/DocumentsRelationManager.php
 [106]  206      8KB       app/Filament/Resources/DocumentRequirementResource.php
 [107]  20       558B      app/Filament/Resources/DocumentRequirementResource/Pages/CreateDocumentRequirement.php
 [108]  28       721B      app/Filament/Resources/DocumentRequirementResource/Pages/EditDocumentRequirement.php
 [109]  20       557B      app/Filament/Resources/DocumentRequirementResource/Pages/ListDocumentRequirements.php
 [110]  231      8KB       app/Filament/Resources/DocumentTypeResource.php
 [111]  20       537B      app/Filament/Resources/DocumentTypeResource/Pages/CreateDocumentType.php
 [112]  28       700B      app/Filament/Resources/DocumentTypeResource/Pages/EditDocumentType.php
 [113]  20       536B      app/Filament/Resources/DocumentTypeResource/Pages/ListDocumentTypes.php
 [114]  176      6KB       app/Filament/Resources/FeeStructureResource.php
 [115]  15       395B      app/Filament/Resources/FeeStructureResource/Pages/CreateFeeStructure.php
 [116]  18       428B      app/Filament/Resources/FeeStructureResource/Pages/EditFeeStructure.php
 [117]  20       516B      app/Filament/Resources/FeeStructureResource/Pages/ListFeeStructures.php
 [118]  227      9KB       app/Filament/Resources/InstallmentPlanResource.php
 [119]  32       926B      app/Filament/Resources/InstallmentPlanResource/Pages/CreateInstallmentPlan.php
 [120]  20       541B      app/Filament/Resources/InstallmentPlanResource/Pages/ListInstallmentPlans.php
 [121]  10       286B      app/Filament/Resources/InstallmentPlanResource/Pages/ViewInstallmentPlan.php
 [122]  96       3KB       app/Filament/Resources/InstallmentPlanResource/RelationManagers/InstallmentsRelationManager.php
 [123]  518      24KB      app/Filament/Resources/MemberCardResource.php
 [124]  76       2KB       app/Filament/Resources/MemberCardResource/Pages/CreateMemberCard.php
 [125]  24       637B      app/Filament/Resources/MemberCardResource/Pages/EditMemberCard.php
 [126]  28       700B      app/Filament/Resources/MemberCardResource/Pages/ListMemberCards.php
 [127]  54       2KB       app/Filament/Resources/MemberCardResource/Pages/ViewMemberCard.php
 [128]  450      20KB      app/Filament/Resources/MemberDocumentResource.php
 [129]  38       1KB       app/Filament/Resources/MemberDocumentResource/Pages/CreateMemberDocument.php
 [130]  43       1KB       app/Filament/Resources/MemberDocumentResource/Pages/EditMemberDocument.php
 [131]  28       730B      app/Filament/Resources/MemberDocumentResource/Pages/ListMemberDocuments.php
 [132]  48       1KB       app/Filament/Resources/MemberDocumentResource/Pages/ViewMemberDocument.php
 [133]  981      50KB      app/Filament/Resources/MemberResource.php
 [134]  47       1KB       app/Filament/Resources/MemberResource/Pages/CreateMember.php
 [135]  53       1KB       app/Filament/Resources/MemberResource/Pages/EditMember.php
 [136]  115      5KB       app/Filament/Resources/MemberResource/Pages/ListMembers.php
 [137]  173      6KB       app/Filament/Resources/MemberResource/Pages/MemberWorkflow.php
 [138]  61       2KB       app/Filament/Resources/MemberResource/Pages/ViewMember.php
 [139]  62       2KB       app/Filament/Resources/MemberResource/RelationManagers/ActivityLogRelationManager.php
 [140]  162      6KB       app/Filament/Resources/MemberResource/RelationManagers/CardsRelationManager.php
 [141]  244      10KB      app/Filament/Resources/MemberResource/RelationManagers/DependentsRelationManager.php
 [142]  151      6KB       app/Filament/Resources/MemberResource/RelationManagers/DocumentsRelationManager.php
 [143]  141      5KB       app/Filament/Resources/MemberResource/RelationManagers/NotesRelationManager.php
 [144]  123      4KB       app/Filament/Resources/MemberResource/RelationManagers/ReceiptsRelationManager.php
 [145]  117      4KB       app/Filament/Resources/MemberResource/RelationManagers/SubscriptionsRelationManager.php
 [146]  76       2KB       app/Filament/Resources/MemberResource/RelationManagers/ViolationsRelationManager.php
 [147]  379      16KB      app/Filament/Resources/MemberSuspensionResource.php
 [148]  23       692B      app/Filament/Resources/MemberSuspensionResource/Pages/CreateMemberSuspension.php
 [149]  46       1KB       app/Filament/Resources/MemberSuspensionResource/Pages/ListMemberSuspensions.php
 [150]  63       2KB       app/Filament/Resources/MemberSuspensionResource/Pages/ViewMemberSuspension.php
 [151]  278      11KB      app/Filament/Resources/NotificationResource.php
 [152]  67       2KB       app/Filament/Resources/NotificationResource/Pages/CreateNotification.php
 [153]  19       489B      app/Filament/Resources/NotificationResource/Pages/ListNotifications.php
 [154]  18       428B      app/Filament/Resources/NotificationResource/Pages/ViewNotification.php
 [155]  380      15KB      app/Filament/Resources/PenaltyResource.php
 [156]  23       647B      app/Filament/Resources/PenaltyResource/Pages/CreatePenalty.php
 [157]  56       2KB       app/Filament/Resources/PenaltyResource/Pages/ListPenalties.php
 [158]  92       3KB       app/Filament/Resources/PenaltyResource/Pages/ViewPenalty.php
 [159]  161      5KB       app/Filament/Resources/PenaltyTypeResource.php
 [160]  17       417B      app/Filament/Resources/PenaltyTypeResource/Pages/CreatePenaltyType.php
 [161]  25       565B      app/Filament/Resources/PenaltyTypeResource/Pages/EditPenaltyType.php
 [162]  20       453B      app/Filament/Resources/PenaltyTypeResource/Pages/ListPenaltyTypes.php
 [163]  492      23KB      app/Filament/Resources/ReceiptResource.php
 [164]  39       1KB       app/Filament/Resources/ReceiptResource/Pages/CreateReceipt.php
 [165]  27       659B      app/Filament/Resources/ReceiptResource/Pages/ListReceipts.php
 [166]  47       1KB       app/Filament/Resources/ReceiptResource/Pages/ViewReceipt.php
 [167]  54       1KB       app/Filament/Resources/ReceiptResource/RelationManagers/ReceiptItemsRelationManager.php
 [168]  173      6KB       app/Filament/Resources/SequenceResource.php
 [169]  16       395B      app/Filament/Resources/SequenceResource/Pages/EditSequence.php
 [170]  30       1KB       app/Filament/Resources/SequenceResource/Pages/ListSequences.php
 [171]  358      15KB      app/Filament/Resources/SubscriptionPeriodResource.php
 [172]  20       565B      app/Filament/Resources/SubscriptionPeriodResource/Pages/CreateSubscriptionPeriod.php
 [173]  29       784B      app/Filament/Resources/SubscriptionPeriodResource/Pages/EditSubscriptionPeriod.php
 [174]  20       557B      app/Filament/Resources/SubscriptionPeriodResource/Pages/ListSubscriptionPeriods.php
 [175]  18       450B      app/Filament/Resources/SubscriptionPeriodResource/Pages/ViewSubscriptionPeriod.php
 [176]  87       3KB       app/Filament/Resources/SubscriptionPeriodResource/RelationManagers/SubscriptionsRelationManager.php
 [177]  552      24KB      app/Filament/Resources/SubscriptionResource.php
 [178]  48       1KB       app/Filament/Resources/SubscriptionResource/Pages/CreateSubscription.php
 [179]  30       824B      app/Filament/Resources/SubscriptionResource/Pages/EditSubscription.php
 [180]  59       2KB       app/Filament/Resources/SubscriptionResource/Pages/ListSubscriptions.php
 [181]  19       504B      app/Filament/Resources/SubscriptionResource/Pages/ViewSubscription.php
 [182]  148      5KB       app/Filament/Resources/SubscriptionResource/RelationManagers/PaymentsRelationManager.php
 [183]  59       2KB       app/Filament/Resources/SubscriptionResource/RelationManagers/SubscriptionReceiptsRelationManager.php
 [184]  203      7KB       app/Filament/Resources/UserResource.php
 [185]  15       363B      app/Filament/Resources/UserResource/Pages/CreateUser.php
 [186]  25       621B      app/Filament/Resources/UserResource/Pages/EditUser.php
 [187]  18       399B      app/Filament/Resources/UserResource/Pages/ListUsers.php
 [188]  18       394B      app/Filament/Resources/UserResource/Pages/ViewUser.php
 [189]  60       2KB       app/Filament/Resources/UserResource/RelationManagers/ActivityLogRelationManager.php
 [190]  597      28KB      app/Filament/Resources/ViolationResource.php
 [191]  23       661B      app/Filament/Resources/ViolationResource/Pages/CreateViolation.php
 [192]  26       625B      app/Filament/Resources/ViolationResource/Pages/EditViolation.php
 [193]  67       2KB       app/Filament/Resources/ViolationResource/Pages/ListViolations.php
 [194]  197      8KB       app/Filament/Resources/ViolationResource/Pages/ViewViolation.php
 [195]  105      3KB       app/Filament/Resources/ViolationResource/RelationManagers/DocumentsRelationManager.php
 [196]  192      7KB       app/Filament/Resources/ViolationResource/RelationManagers/PenaltiesRelationManager.php
 [197]  151      5KB       app/Filament/Resources/ViolationTypeResource.php
 [198]  17       425B      app/Filament/Resources/ViolationTypeResource/Pages/CreateViolationType.php
 [199]  25       573B      app/Filament/Resources/ViolationTypeResource/Pages/EditViolationType.php
 [200]  20       461B      app/Filament/Resources/ViolationTypeResource/Pages/ListViolationTypes.php
 [201]  53       2KB       app/Filament/Widgets/AdminStatsWidget.php
 [202]  63       1KB       app/Filament/Widgets/BoardOffersChartWidget.php
 [203]  56       2KB       app/Filament/Widgets/CardStatsWidget.php
 [204]  73       1KB       app/Filament/Widgets/CollectionByTypeWidget.php
 [205]  53       2KB       app/Filament/Widgets/DailyReceiptsSummaryWidget.php
 [206]  82       2KB       app/Filament/Widgets/DependentRelationshipChartWidget.php
 [207]  83       3KB       app/Filament/Widgets/DependentStatsWidget.php
 [208]  81       2KB       app/Filament/Widgets/DisciplinaryOverviewWidget.php
 [209]  59       2KB       app/Filament/Widgets/DocumentComplianceWidget.php
 [210]  69       1KB       app/Filament/Widgets/FinancialChartWidget.php
 [211]  82       3KB       app/Filament/Widgets/MemberStatsOverview.php
 [212]  87       2KB       app/Filament/Widgets/MemberWorkflowFunnel.php
 [213]  95       2KB       app/Filament/Widgets/MembershipGrowthChart.php
 [214]  69       1KB       app/Filament/Widgets/MembershipStatusChart.php
 [215]  60       1KB       app/Filament/Widgets/OverdueInstallmentsWidget.php
 [216]  69       2KB       app/Filament/Widgets/PendingActionsWidget.php
 [217]  88       3KB       app/Filament/Widgets/PendingDependentsWidget.php
 [218]  51       1KB       app/Filament/Widgets/RecentActivityWidget.php
 [219]  65       2KB       app/Filament/Widgets/RecentMembersWidget.php
 [220]  69       2KB       app/Filament/Widgets/RecentViolationsWidget.php
 [221]  66       1KB       app/Filament/Widgets/RevenueByCategoryChart.php
 [222]  76       2KB       app/Filament/Widgets/RevenueChart.php
 [223]  90       3KB       app/Filament/Widgets/StatsOverviewWidget.php
 [224]  76       3KB       app/Filament/Widgets/SubscriptionOverviewWidget.php
 [225]  77       2KB       app/Filament/Widgets/SubscriptionStatusChart.php
 [226]  60       2KB       app/Filament/Widgets/UpcomingExpirationsWidget.php
 [227]  46       1KB       app/Filament/Widgets/ViolationsOverviewWidget.php
 [228]  80       1KB       app/Helpers/DateHelper.php
 [229]  112      3KB       app/Helpers/MoneyHelper.php
 [230]  76       1KB       app/Helpers/NationalIdHelper.php
 [231]  62       1KB       app/Helpers/SettingsHelper.php
 [232]  44       1KB       app/Http/Controllers/Api/V1/CardVerificationController.php
 [233]  70       2KB       app/Http/Controllers/Api/V1/MemberLookupController.php
 [234]  59       1KB       app/Http/Controllers/CardController.php
 [235]  46       1KB       app/Http/Controllers/MemberPrintController.php
 [236]  71       1KB       app/Http/Controllers/ReceiptController.php
 [237]  24       633B      app/Http/Middleware/CheckUserActive.php
 [238]  25       612B      app/Http/Middleware/TrackLastLogin.php
 [239]  252      9KB       app/Imports/MembersImport.php
 [240]  44       915B      app/Models/BackupRecord.php
 [241]  79       1KB       app/Models/BoardDecision.php
 [242]  124      3KB       app/Models/BoardOffer.php
 [243]  73       1KB       app/Models/Booking.php
 [244]  83       1KB       app/Models/Card.php
 [245]  38       833B      app/Models/CardType.php
 [246]  89       2KB       app/Models/CashRegister.php
 [247]  55       1KB       app/Models/CashRegisterTransaction.php
 [248]  91       2KB       app/Models/Dependent.php
 [249]  75       1KB       app/Models/Document.php
 [250]  45       1KB       app/Models/DocumentType.php
 [251]  29       607B      app/Models/EducationalQualification.php
 [252]  30       615B      app/Models/ExitReason.php
 [253]  49       1KB       app/Models/Facility.php
 [254]  42       905B      app/Models/FeeItem.php
 [255]  57       1KB       app/Models/FeeSchedule.php
 [256]  60       1KB       app/Models/FeeStructure.php
 [257]  53       1KB       app/Models/FiscalYear.php
 [258]  30       622B      app/Models/Governorate.php
 [259]  50       1KB       app/Models/ImportJob.php
 [260]  48       1KB       app/Models/Installment.php
 [261]  101      2KB       app/Models/InstallmentPlan.php
 [262]  63       1KB       app/Models/Interview.php
 [263]  30       639B      app/Models/MaritalStatus.php
 [264]  335      9KB       app/Models/Member.php
 [265]  94       2KB       app/Models/MemberSuspension.php
 [266]  44       1KB       app/Models/MembershipStatus.php
 [267]  65       1KB       app/Models/MembershipType.php
 [268]  30       614B      app/Models/Nationality.php
 [269]  75       1KB       app/Models/Notification.php
 [270]  33       659B      app/Models/NotificationTemplate.php
 [271]  42       956B      app/Models/NumberingSequence.php
 [272]  96       2KB       app/Models/Penalty.php
 [273]  28       669B      app/Models/PenaltyType.php
 [274]  159      3KB       app/Models/Receipt.php
 [275]  31       645B      app/Models/ReceiptItem.php
 [276]  32       681B      app/Models/RelationshipType.php
 [277]  29       591B      app/Models/Religion.php
 [278]  45       1KB       app/Models/Sequence.php
 [279]  271      8KB       app/Models/Subscription.php
 [280]  101      3KB       app/Models/SubscriptionPayment.php
 [281]  157      4KB       app/Models/SubscriptionPeriod.php
 [282]  30       621B      app/Models/SuspensionReason.php
 [283]  56       1KB       app/Models/SystemSetting.php
 [284]  49       1KB       app/Models/Traits/Archivable.php
 [285]  17       344B      app/Models/Traits/HasActiveScope.php
 [286]  33       973B      app/Models/Traits/HasAuditTrail.php
 [287]  24       648B      app/Models/Traits/HasBilingualName.php
 [288]  17       385B      app/Models/Traits/HasDisplayOrder.php
 [289]  24       621B      app/Models/Traits/HasFiscalYear.php
 [290]  17       394B      app/Models/Traits/HasMemberScope.php
 [291]  29       869B      app/Models/Traits/HasNumberingSequence.php
 [292]  87       2KB       app/Models/Transfer.php
 [293]  58       1KB       app/Models/User.php
 [294]  109      3KB       app/Models/Violation.php
 [295]  20       408B      app/Models/ViolationDocument.php
 [296]  28       668B      app/Models/ViolationType.php
 [297]  53       1KB       app/Models/WorkflowProgress.php
 [298]  42       973B      app/Models/WorkflowStage.php
 [299]  34       945B      app/Observers/BoardOfferObserver.php
 [300]  20       513B      app/Observers/CashRegisterObserver.php
 [301]  46       1KB       app/Observers/DependentObserver.php
 [302]  37       991B      app/Observers/MemberCardObserver.php
 [303]  38       1KB       app/Observers/MemberDocumentObserver.php
 [304]  92       2KB       app/Observers/MemberObserver.php
 [305]  21       453B      app/Observers/MemberSuspensionObserver.php
 [306]  25       532B      app/Observers/PenaltyObserver.php
 [307]  65       2KB       app/Observers/ReceiptObserver.php
 [308]  67       2KB       app/Observers/SubscriptionObserver.php
 [309]  32       859B      app/Observers/ViolationObserver.php
 [310]  36       821B      app/Policies/BoardDecisionPolicy.php
 [311]  44       930B      app/Policies/BoardOfferPolicy.php
 [312]  38       727B      app/Policies/CardPolicy.php
 [313]  40       883B      app/Policies/CashRegisterPolicy.php
 [314]  104      2KB       app/Policies/DependentPolicy.php
 [315]  71       1KB       app/Policies/DocumentRequirementPolicy.php
 [316]  71       1KB       app/Policies/DocumentTypePolicy.php
 [317]  71       1KB       app/Policies/MemberCardPolicy.php
 [318]  71       1KB       app/Policies/MemberDocumentPolicy.php
 [319]  97       2KB       app/Policies/MemberPolicy.php
 [320]  48       1KB       app/Policies/MemberSuspensionPolicy.php
 [321]  31       677B      app/Policies/NotificationPolicy.php
 [322]  48       1018B     app/Policies/PenaltyPolicy.php
 [323]  38       836B      app/Policies/PenaltyTypePolicy.php
 [324]  43       972B      app/Policies/ReceiptPolicy.php
 [325]  26       533B      app/Policies/SequencePolicy.php
 [326]  59       1KB       app/Policies/SubscriptionPeriodPolicy.php
 [327]  65       1KB       app/Policies/SubscriptionPolicy.php
 [328]  36       818B      app/Policies/SystemSettingPolicy.php
 [329]  33       663B      app/Policies/TransferPolicy.php
 [330]  53       1KB       app/Policies/UserPolicy.php
 [331]  58       1KB       app/Policies/ViolationPolicy.php
 [332]  38       862B      app/Policies/ViolationTypePolicy.php
 [333]  35       1KB       app/Providers/AdminServiceProvider.php
 [334]  45       1KB       app/Providers/AppServiceProvider.php
 [335]  33       915B      app/Providers/AuthServiceProvider.php
 [336]  23       417B      app/Providers/CardsAndDocumentsServiceProvider.php
 [337]  32       937B      app/Providers/DisciplinaryServiceProvider.php
 [338]  19       342B      app/Providers/EventServiceProvider.php
 [339]  131      5KB       app/Providers/Filament/AdminPanelProvider.php
 [340]  45       1KB       app/Providers/FinancialServiceProvider.php
 [341]  43       1KB       app/Providers/MemberServiceProvider.php
 [342]  23       632B      app/Providers/Phase5ServiceProvider.php
 [343]  38       1KB       app/Providers/SubscriptionServiceProvider.php
 [344]  89       2KB       app/Services/Admin/AuditService.php
 [345]  174      5KB       app/Services/Admin/BackupService.php
 [346]  195      7KB       app/Services/Admin/BoardService.php
 [347]  17       500B      app/Services/Admin/CashRegisterService.php
 [348]  188      6KB       app/Services/Admin/NotificationDispatchService.php
 [349]  17       476B      app/Services/Admin/SequenceService.php
 [350]  102      2KB       app/Services/Admin/SettingsService.php
 [351]  449      15KB      app/Services/Cards/CardService.php
 [352]  283      9KB       app/Services/Disciplinary/PenaltyService.php
 [353]  264      8KB       app/Services/Disciplinary/SuspensionService.php
 [354]  315      10KB      app/Services/Disciplinary/ViolationService.php
 [355]  500      17KB      app/Services/Documents/DocumentService.php
 [356]  170      6KB       app/Services/Financial/CashRegisterService.php
 [357]  331      12KB      app/Services/Financial/FeeCalculationService.php
 [358]  199      8KB       app/Services/Financial/InstallmentService.php
 [359]  63       1KB       app/Services/Financial/NumberingService.php
 [360]  311      12KB      app/Services/Financial/ReceiptService.php
 [361]  92       2KB       app/Services/Financial/SequenceService.php
 [362]  264      10KB      app/Services/Financial/SubscriptionService.php
 [363]  419      14KB      app/Services/Membership/DependentService.php
 [364]  74       2KB       app/Services/Membership/MemberNumberService.php
 [365]  182      6KB       app/Services/Membership/MembershipService.php
 [366]  509      18KB      app/Services/Membership/MembershipWorkflowService.php
 [367]  58       1KB       app/Services/Notifications/NotificationService.php
 [368]  286      10KB      app/Services/Reporting/DashboardStatisticsService.php
 [369]  254      8KB       app/Services/Reporting/FinancialReportService.php
 [370]  206      7KB       app/Services/Reporting/MembershipReportService.php
 [371]  117      4KB       app/Services/Reporting/ReportService.php
 [372]  133      4KB       app/Services/Reporting/SubscriptionReportService.php
 [373]  141      4KB       app/Services/Reporting/ViolationReportService.php
 [374]  174      6KB       app/Services/Subscription/RenewalService.php
 [375]  171      5KB       app/Services/Subscription/SubscriptionCalculatorService.php
 [376]  200      8KB       app/Services/Subscription/SubscriptionService.php
 [377]  6        144B      bootstrap/providers.php
 [378]  2        54B       code.txt
 [379]  0        61B       code_10.txt
 [380]  2        41B       code_11.txt
 [381]  0        35B       code_12.txt
 [382]  2        29B       code_13.txt
 [383]  2        26B       code_2.txt
 [384]  2        35B       code_3.txt
 [385]  2        31B       code_4.txt
 [386]  2        48B       code_5.txt
 [387]  2        25B       code_6.txt
 [388]  2        37B       code_7.txt
 [389]  2        28B       code_8.txt
 [390]  2        37B       code_9.txt
 [391]  595      28KB      codecoll.sh
 [392]  78       2KB       composer.json
 [393]  73       2KB       config/club.php
 [394]  28       776B      database/factories/CardFactory.php
 [395]  65       2KB       database/factories/DependentFactory.php
 [396]  96       4KB       database/factories/MemberFactory.php
 [397]  40       1KB       database/factories/ReceiptFactory.php
 [398]  37       1KB       database/factories/SubscriptionFactory.php
 [399]  35       1KB       database/migrations/2024_01_01_000001_create_membership_types_table.php
 [400]  32       1KB       database/migrations/2024_01_01_000002_create_membership_statuses_table.php
 [401]  27       873B      database/migrations/2024_01_01_000003_create_nationalities_table.php
 [402]  25       706B      database/migrations/2024_01_01_000004_create_religions_table.php
 [403]  25       720B      database/migrations/2024_01_01_000005_create_marital_statuses_table.php
 [404]  26       762B      database/migrations/2024_01_01_000006_create_governorates_table.php
 [405]  25       740B      database/migrations/2024_01_01_000007_create_educational_qualifications_table.php
 [406]  32       1KB       database/migrations/2024_01_01_000008_create_document_types_table.php
 [407]  89       3KB       database/migrations/2024_01_01_000010_create_members_table.php
 [408]  42       1KB       database/migrations/2024_01_01_000011_create_documents_table.php
 [409]  42       1KB       database/migrations/2024_01_01_000012_create_cards_table.php
 [410]  52       2KB       database/migrations/2024_01_01_000013_create_receipts_table.php
 [411]  29       958B      database/migrations/2024_01_01_000014_create_receipt_items_table.php
 [412]  44       1KB       database/migrations/2024_01_01_000015_create_fee_structures_table.php
 [413]  42       1KB       database/migrations/2024_01_01_000016_create_workflow_stages_table.php
 [414]  35       1KB       database/migrations/2024_01_01_000017_create_interviews_table.php
 [415]  44       1KB       database/migrations/2024_01_01_000018_create_transfers_table.php
 [416]  42       1KB       database/migrations/2024_01_01_000019_create_installment_plans_table.php
 [417]  32       1KB       database/migrations/2024_01_01_000020_create_installments_table.php
 [418]  44       1KB       database/migrations/2024_01_01_000021_create_board_offers_table.php
 [419]  44       1KB       database/migrations/2024_01_01_000022_create_bookings_table.php
 [420]  41       1KB       database/migrations/2024_01_01_000023_create_facilities_table.php
 [421]  26       768B      database/migrations/2024_01_01_000024_create_payment_methods_table.php
 [422]  35       1KB       database/migrations/2024_01_01_000025_create_cash_registers_table.php
 [423]  42       1KB       database/migrations/2024_01_01_040001_create_subscription_periods_table.php
 [424]  52       2KB       database/migrations/2024_01_01_040002_create_subscriptions_table.php
 [425]  36       1KB       database/migrations/2024_01_01_040003_create_subscription_payments_table.php
 [426]  29       985B      database/migrations/2024_01_01_200001_create_violation_types_table.php
 [427]  30       1KB       database/migrations/2024_01_01_200002_create_penalty_types_table.php
 [428]  68       2KB       database/migrations/2024_01_01_200003_create_violations_table.php
 [429]  68       2KB       database/migrations/2024_01_01_200004_create_penalties_table.php
 [430]  51       1KB       database/migrations/2024_01_01_200005_create_member_suspensions_table.php
 [431]  27       791B      database/migrations/2024_01_01_200006_create_violation_documents_table.php
 [432]  31       1KB       database/migrations/2024_01_15_000001_create_cash_register_transactions_table.php
 [433]  37       1KB       database/migrations/2024_01_15_000002_create_club_notifications_table.php
 [434]  30       953B      database/migrations/2024_01_15_000002_create_receipt_items_table.php
 [435]  35       1KB       database/migrations/2024_01_15_000003_add_admin_fields_to_users_table.php
 [436]  36       989B      database/seeders/DatabaseSeeder.php
 [437]  141      6KB       database/seeders/DocumentTypeSeeder.php
 [438]  32       1KB       database/seeders/EducationalQualificationSeeder.php
 [439]  148      5KB       database/seeders/FeeStructureSeeder.php
 [440]  48       2KB       database/seeders/GovernorateSeeder.php
 [441]  25       736B      database/seeders/MaritalStatusSeeder.php
 [442]  165      6KB       database/seeders/MembershipStatusSeeder.php
 [443]  112      4KB       database/seeders/MembershipTypeSeeder.php
 [444]  44       2KB       database/seeders/NationalitySeeder.php
 [445]  27       1KB       database/seeders/PaymentMethodSeeder.php
 [446]  119      5KB       database/seeders/PenaltyTypeSeeder.php
 [447]  25       729B      database/seeders/ReligionSeeder.php
 [448]  119      5KB       database/seeders/RolesAndPermissionsSeeder.php
 [449]  86       16KB      database/seeders/SystemSettingsSeeder.php
 [450]  120      5KB       database/seeders/ViolationTypeSeeder.php
 [451]  5        92B       postcss.config.js
 [452]  2        58B       resources/css/app.css
 [453]  34       804B      resources/css/filament/admin/theme.css
 [454]  1        55B       resources/js/app.js
 [455]  97       5KB       resources/views/filament/pages/backup-management.blade.php
 [456]  180      11KB      resources/views/filament/pages/card-preview.blade.php
 [457]  140      9KB       resources/views/filament/pages/document-checklist.blade.php
 [458]  16       1KB       resources/views/filament/pages/reports/aging-report.blade.php
 [459]  83       4KB       resources/views/filament/pages/reports/churn-report.blade.php
 [460]  94       4KB       resources/views/filament/pages/reports/financial-report.blade.php
 [461]  17       723B      resources/views/filament/pages/reports/installment-report.blade.php
 [462]  68       3KB       resources/views/filament/pages/reports/membership-report.blade.php
 [463]  90       4KB       resources/views/filament/pages/reports/subscription-report.blade.php
 [464]  68       3KB       resources/views/filament/pages/reports/top-paying-members.blade.php
 [465]  120      5KB       resources/views/filament/pages/reports/violations-report.blade.php
 [466]  126      4KB       resources/views/filament/pages/system-settings.blade.php
 [467]  164      9KB       resources/views/filament/resources/member-resource/pages/member-workflow.blade.php
 [468]  11       353B      resources/views/filament/widgets/recent-activity-widget.blade.php
 [469]  269      8KB       resources/views/pdf/receipt.blade.php
 [470]  282      9KB       resources/views/prints/member-profile.blade.php
 [471]  64       2KB       resources/views/reports/aging.blade.php
 [472]  70       3KB       resources/views/reports/churn.blade.php
 [473]  102      4KB       resources/views/reports/financial.blade.php
 [474]  53       2KB       resources/views/reports/installments.blade.php
 [475]  100      4KB       resources/views/reports/membership.blade.php
 [476]  80       3KB       resources/views/reports/subscriptions.blade.php
 [477]  54       2KB       resources/views/reports/top-paying-members.blade.php
 [478]  78       3KB       resources/views/reports/violations.blade.php
 [479]  20       796B      routes/api.php
 [480]  6        231B      routes/console.php
 [481]  26       984B      routes/member-routes.php
 [482]  15       584B      routes/web.php
 [483]  13       364B      scripts/cleanup-root-junk.sh
 [484]  30       916B      tailwind.config.js
 [485]  22       583B      tests/Feature/Controllers/CardControllerTest.php
 [486]  34       984B      tests/Feature/Controllers/ReceiptControllerTest.php
 [487]  28       842B      tests/Feature/Imports/MembersImportTest.php
 [488]  11       186B      tests/Pest.php
 [489]  18       350B      tests/TestCase.php
 [490]  18       488B      tests/Unit/Helpers/MoneyHelperTest.php
 [491]  21       623B      tests/Unit/Helpers/NationalIdHelperTest.php
 [492]  34       968B      tests/Unit/Models/MemberTest.php
 [493]  10       283B      tests/Unit/Services/FeeCalculationServiceTest.php
 [494]  18       499B      vite.config.js


╔══════════════════════════════════════════════════════════════════════════════╗
║  SECTION 3: FILE TYPE BREAKDOWN                                             ║
╚══════════════════════════════════════════════════════════════════════════════╝

 EXTENSION/TYPE             COUNT
 ───────────────────────    ─────
 .php                       471
 .txt                       13
 .js                        4
 .sh                        2
 .css                       2
 .json                      1
 .example                   1


╔══════════════════════════════════════════════════════════════════════════════╗
║  SECTION 5: COMPLETE FILE CONTENTS                                          ║
║                                                                              ║
║  Every file printed in full with:                                            ║
║    • Clear start/end markers                                                 ║
║    • File path, size, line count in the header                               ║
║    • Language hint for syntax context                                        ║
╚══════════════════════════════════════════════════════════════════════════════╝


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [001/494]: .env.example
│ LANGUAGE: dotenv | LINES: 52 | SIZE: 1086 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	APP_NAME="نادي الآركيد"
     2	APP_ENV=local
     3	APP_KEY=
     4	APP_DEBUG=true
     5	APP_TIMEZONE=Africa/Cairo
     6	APP_URL=http://localhost
     7	APP_LOCALE=ar
     8	APP_FALLBACK_LOCALE=en
     9	
    10	LOG_CHANNEL=stack
    11	LOG_STACK=single
    12	LOG_DEPRECATIONS_CHANNEL=null
    13	LOG_LEVEL=debug
    14	
    15	DB_CONNECTION=mysql
    16	DB_HOST=srv-captain--mysql-db
    17	DB_PORT=3306
    18	DB_DATABASE=club_management
    19	DB_USERNAME=club_admin
    20	DB_PASSWORD=Alarcade123#
    21	
    22	SESSION_DRIVER=database
    23	SESSION_LIFETIME=120
    24	
    25	BROADCAST_CONNECTION=log
    26	FILESYSTEM_DISK=local
    27	QUEUE_CONNECTION=database
    28	
    29	CACHE_STORE=file
    30	CACHE_PREFIX=club_
    31	
    32	MAIL_MAILER=smtp
    33	MAIL_HOST=mailpit
    34	MAIL_PORT=1025
    35	MAIL_USERNAME=null
    36	MAIL_PASSWORD=null
    37	MAIL_ENCRYPTION=null
    38	MAIL_FROM_ADDRESS="noreply@club.local"
    39	MAIL_FROM_NAME="${APP_NAME}"
    40	
    41	# Club-specific
    42	CLUB_NAME_AR="نادي الآركيد"
    43	CLUB_NAME_EN="AL-ARCADE Club"
    44	CLUB_CURRENCY=EGP
    45	CLUB_CURRENCY_NAME_AR="جنيه"
    46	CLUB_CURRENCY_SUBUNIT_AR="قرش"
    47	CLUB_FISCAL_YEAR_START_MONTH=1
    48	CLUB_FISCAL_YEAR_START_DAY=1
    49	CLUB_DEFAULT_LANGUAGE=ar
    50	CLUB_DATE_FORMAT=d/m/Y
    51	CLUB_MAX_DEPENDENTS_PER_MEMBER=10
    52	CLUB_MEMBER_PHOTO_MAX_KB=2048
    53	CLUB_DOCUMENT_MAX_KB=5120│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [001]: .env.example


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [002/494]: app/Actions/Subscription/ApplyLateFeesAction.php
│ LANGUAGE: php | LINES: 20 | SIZE: 466 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Actions\Subscription;
     4	
     5	use App\Models\SubscriptionPeriod;
     6	use App\Services\Subscription\RenewalService;
     7	
     8	class ApplyLateFeesAction
     9	{
    10	    public function __construct(
    11	        protected RenewalService $renewalService,
    12	    ) {}
    13	
    14	    /**
    15	     * Apply late fees to all overdue subscriptions in a period.
    16	     */
    17	    public function execute(SubscriptionPeriod $period): array
    18	    {
    19	        return $this->renewalService->applyLateFees($period);
    20	    }
    21	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [002]: app/Actions/Subscription/ApplyLateFeesAction.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [003/494]: app/Actions/Subscription/BulkRenewSubscriptions.php
│ LANGUAGE: php | LINES: 20 | SIZE: 469 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Actions\Subscription;
     4	
     5	use App\Models\SubscriptionPeriod;
     6	use App\Services\Subscription\RenewalService;
     7	
     8	class BulkRenewSubscriptions
     9	{
    10	    public function __construct(
    11	        protected RenewalService $renewalService,
    12	    ) {}
    13	
    14	    /**
    15	     * Generate subscriptions for all active members in a period.
    16	     */
    17	    public function execute(SubscriptionPeriod $period): array
    18	    {
    19	        return $this->renewalService->bulkGenerate($period);
    20	    }
    21	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [003]: app/Actions/Subscription/BulkRenewSubscriptions.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [004/494]: app/Actions/Subscription/CalculateSubscriptionFees.php
│ LANGUAGE: php | LINES: 21 | SIZE: 566 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Actions\Subscription;
     4	
     5	use App\Models\Member;
     6	use App\Models\SubscriptionPeriod;
     7	use App\Services\Subscription\SubscriptionCalculatorService;
     8	
     9	class CalculateSubscriptionFees
    10	{
    11	    public function __construct(
    12	        protected SubscriptionCalculatorService $calculator,
    13	    ) {}
    14	
    15	    /**
    16	     * Execute fee calculation and return structured result.
    17	     */
    18	    public function execute(Member $member, SubscriptionPeriod $period, float $discount = 0): array
    19	    {
    20	        return $this->calculator->calculate($member, $period, $discount);
    21	    }
    22	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [004]: app/Actions/Subscription/CalculateSubscriptionFees.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [005/494]: app/Actions/Subscription/RenewMemberSubscription.php
│ LANGUAGE: php | LINES: 33 | SIZE: 870 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Actions\Subscription;
     4	
     5	use App\Models\Member;
     6	use App\Models\Subscription;
     7	use App\Models\SubscriptionPeriod;
     8	use App\Services\Subscription\SubscriptionService;
     9	
    10	class RenewMemberSubscription
    11	{
    12	    public function __construct(
    13	        protected SubscriptionService $subscriptionService,
    14	    ) {}
    15	
    16	    /**
    17	     * Create a subscription for a specific member and period.
    18	     */
    19	    public function execute(
    20	        Member $member,
    21	        SubscriptionPeriod $period,
    22	        float $discountAmount = 0,
    23	        ?string $discountReason = null,
    24	        ?string $notes = null,
    25	    ): Subscription {
    26	        return $this->subscriptionService->createSubscription(
    27	            member: $member,
    28	            period: $period,
    29	            discountAmount: $discountAmount,
    30	            discountReason: $discountReason,
    31	            notes: $notes,
    32	        );
    33	    }
    34	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [005]: app/Actions/Subscription/RenewMemberSubscription.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [006/494]: app/Casts/ArabicDateCast.php
│ LANGUAGE: php | LINES: 27 | SIZE: 683 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Casts;
     4	
     5	use Carbon\Carbon;
     6	use Illuminate\Contracts\Database\Eloquent\CastsAttributes;
     7	use Illuminate\Database\Eloquent\Model;
     8	
     9	class ArabicDateCast implements CastsAttributes
    10	{
    11	    public function get(Model $model, string $key, mixed $value, array $attributes): ?string
    12	    {
    13	        if ($value === null) {
    14	            return null;
    15	        }
    16	
    17	        return Carbon::parse($value)->format(config('club.date_format', 'd/m/Y'));
    18	    }
    19	
    20	    public function set(Model $model, string $key, mixed $value, array $attributes): ?string
    21	    {
    22	        if ($value === null) {
    23	            return null;
    24	        }
    25	
    26	        return Carbon::parse($value)->format('Y-m-d');
    27	    }
    28	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [006]: app/Casts/ArabicDateCast.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [007/494]: app/Casts/MoneyCast.php
│ LANGUAGE: php | LINES: 26 | SIZE: 619 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Casts;
     4	
     5	use Illuminate\Contracts\Database\Eloquent\CastsAttributes;
     6	use Illuminate\Database\Eloquent\Model;
     7	
     8	class MoneyCast implements CastsAttributes
     9	{
    10	    public function get(Model $model, string $key, mixed $value, array $attributes): ?float
    11	    {
    12	        if ($value === null) {
    13	            return null;
    14	        }
    15	
    16	        return round((float) $value, 2);
    17	    }
    18	
    19	    public function set(Model $model, string $key, mixed $value, array $attributes): ?string
    20	    {
    21	        if ($value === null) {
    22	            return null;
    23	        }
    24	
    25	        return number_format((float) $value, 2, '.', '');
    26	    }
    27	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [007]: app/Casts/MoneyCast.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [008/494]: app/Console/Commands/ApplySubscriptionLateFees.php
│ LANGUAGE: php | LINES: 45 | SIZE: 1496 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Console\Commands;
     4	
     5	use App\Models\SubscriptionPeriod;
     6	use App\Services\Subscription\RenewalService;
     7	use Illuminate\Console\Command;
     8	
     9	class ApplySubscriptionLateFees extends Command
    10	{
    11	    protected $signature = 'subscriptions:apply-late-fees {--period= : Specific period ID}';
    12	    protected $description = 'Apply late fees to overdue subscriptions past grace period';
    13	
    14	    public function handle(RenewalService $renewalService): int
    15	    {
    16	        $periodId = $this->option('period');
    17	
    18	        $periods = $periodId
    19	            ? SubscriptionPeriod::where('id', $periodId)->get()
    20	            : SubscriptionPeriod::active()->get();
    21	
    22	        if ($periods->isEmpty()) {
    23	            $this->warn('لا توجد فترات اشتراك نشطة.');
    24	            return self::SUCCESS;
    25	        }
    26	
    27	        foreach ($periods as $period) {
    28	            $this->info("Processing period: {$period->year} ({$period->name_ar})");
    29	
    30	            $results = $renewalService->applyLateFees($period);
    31	
    32	            $this->info("  Applied: {$results['applied']}");
    33	            $this->info("  Skipped: {$results['skipped']}");
    34	
    35	            if (count($results['errors']) > 0) {
    36	                $this->warn("  Errors: " . count($results['errors']));
    37	                foreach ($results['errors'] as $error) {
    38	                    $this->error("    Member #{$error['member_id']}: {$error['error']}");
    39	                }
    40	            }
    41	        }
    42	
    43	        $this->info('Done.');
    44	        return self::SUCCESS;
    45	    }
    46	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [008]: app/Console/Commands/ApplySubscriptionLateFees.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [009/494]: app/Console/Commands/AutoExpireCards.php
│ LANGUAGE: php | LINES: 21 | SIZE: 555 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Console\Commands;
     4	
     5	use App\Services\Cards\CardService;
     6	use Illuminate\Console\Command;
     7	
     8	class AutoExpireCards extends Command
     9	{
    10	    protected $signature = 'cards:auto-expire';
    11	
    12	    protected $description = 'تلقائياً تحديث حالة الكروت منتهية الصلاحية';
    13	
    14	    public function handle(CardService $cardService): int
    15	    {
    16	        $count = $cardService->autoExpireCards();
    17	
    18	        $this->info("تم تحديث {$count} كارنيه منتهي الصلاحية.");
    19	
    20	        return self::SUCCESS;
    21	    }
    22	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [009]: app/Console/Commands/AutoExpireCards.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [010/494]: app/Console/Commands/CheckExpiringDocuments.php
│ LANGUAGE: php | LINES: 35 | SIZE: 1248 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Console\Commands;
     4	
     5	use App\Services\Documents\DocumentService;
     6	use Illuminate\Console\Command;
     7	
     8	class CheckExpiringDocuments extends Command
     9	{
    10	    protected $signature = 'documents:check-expiring {--days=30 : عدد الأيام قبل الانتهاء}';
    11	
    12	    protected $description = 'فحص المستندات التي تقترب من تاريخ الانتهاء';
    13	
    14	    public function handle(DocumentService $documentService): int
    15	    {
    16	        $days = (int) $this->option('days');
    17	        $expiring = $documentService->getExpiringDocuments($days);
    18	        $expired = $documentService->getExpiredDocuments();
    19	
    20	        $this->info("مستندات تنتهي خلال {$days} يوم: {$expiring->count()}");
    21	        $this->info("مستندات منتهية: {$expired->count()}");
    22	
    23	        if ($expiring->count() > 0) {
    24	            $this->table(
    25	                ['العضو', 'نوع المستند', 'تاريخ الانتهاء'],
    26	                $expiring->map(fn ($doc) => [
    27	                    $doc->member?->full_name_ar ?? '-',
    28	                    $doc->documentType?->name_ar ?? '-',
    29	                    $doc->expiry_date,
    30	                ])->toArray()
    31	            );
    32	        }
    33	
    34	        return self::SUCCESS;
    35	    }
    36	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [010]: app/Console/Commands/CheckExpiringDocuments.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [011/494]: app/Console/Commands/CleanOldBackups.php
│ LANGUAGE: php | LINES: 25 | SIZE: 669 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Console\Commands;
     4	
     5	use App\Services\Admin\BackupService;
     6	use Illuminate\Console\Command;
     7	
     8	class CleanOldBackups extends Command
     9	{
    10	    protected $signature = 'admin:clean-backups {--keep=10 : Number of backups to keep}';
    11	
    12	    protected $description = 'تنظيف النسخ الاحتياطية القديمة';
    13	
    14	    public function handle(BackupService $backupService): int
    15	    {
    16	        $keep = (int) $this->option('keep');
    17	
    18	        $this->info("الاحتفاظ بآخر {$keep} نسخة...");
    19	
    20	        $count = $backupService->cleanOldBackups($keep);
    21	
    22	        $this->info("تم حذف {$count} نسخة.");
    23	
    24	        return self::SUCCESS;
    25	    }
    26	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [011]: app/Console/Commands/CleanOldBackups.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [012/494]: app/Console/Commands/CompletePenaltiesCommand.php
│ LANGUAGE: php | LINES: 23 | SIZE: 580 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	declare(strict_types=1);
     4	
     5	namespace App\Console\Commands;
     6	
     7	use App\Services\Disciplinary\PenaltyService;
     8	use Illuminate\Console\Command;
     9	
    10	class CompletePenaltiesCommand extends Command
    11	{
    12	    protected $signature = 'disciplinary:complete-penalties';
    13	
    14	    protected $description = 'Auto-complete penalties that have expired and have no outstanding fines';
    15	
    16	    public function handle(PenaltyService $service): int
    17	    {
    18	        $count = $service->autoCompleteExpiredPenalties();
    19	
    20	        $this->info("Completed {$count} penalty(ies).");
    21	
    22	        return self::SUCCESS;
    23	    }
    24	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [012]: app/Console/Commands/CompletePenaltiesCommand.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [013/494]: app/Console/Commands/CreateBackup.php
│ LANGUAGE: php | LINES: 29 | SIZE: 804 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Console\Commands;
     4	
     5	use App\Services\Admin\BackupService;
     6	use Illuminate\Console\Command;
     7	
     8	class CreateBackup extends Command
     9	{
    10	    protected $signature = 'admin:backup {--type=full : Backup type (full/db)}';
    11	
    12	    protected $description = 'إنشاء نسخة احتياطية لقاعدة البيانات';
    13	
    14	    public function handle(BackupService $backupService): int
    15	    {
    16	        $type = $this->option('type');
    17	
    18	        $this->info("إنشاء نسخة احتياطية ({$type})...");
    19	
    20	        $result = $backupService->createBackup($type);
    21	
    22	        if ($result['success']) {
    23	            $this->info("تم بنجاح: {$result['filename']}");
    24	            return self::SUCCESS;
    25	        }
    26	
    27	        $this->error("فشل: {$result['error']}");
    28	        return self::FAILURE;
    29	    }
    30	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [013]: app/Console/Commands/CreateBackup.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [014/494]: app/Console/Commands/ExpireOverdueApplicationsCommand.php
│ LANGUAGE: php | LINES: 27 | SIZE: 856 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Console\Commands;
     4	
     5	use App\Services\Membership\MembershipWorkflowService;
     6	use Illuminate\Console\Command;
     7	
     8	class ExpireOverdueApplicationsCommand extends Command
     9	{
    10	    protected $signature = 'members:expire-overdue';
    11	
    12	    protected $description = 'تنتهي صلاحية الطلبات التي تجاوزت مهلة 15 يوم بعد موافقة المجلس بدون سداد';
    13	
    14	    public function handle(MembershipWorkflowService $service): int
    15	    {
    16	        $this->info('جاري فحص الطلبات المتأخرة...');
    17	
    18	        $count = $service->expireOverdueApplications();
    19	
    20	        if ($count > 0) {
    21	            $this->warn("تم إنهاء {$count} طلب متأخر عن السداد");
    22	        } else {
    23	            $this->info('لا توجد طلبات متأخرة');
    24	        }
    25	
    26	        return self::SUCCESS;
    27	    }
    28	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [014]: app/Console/Commands/ExpireOverdueApplicationsCommand.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [015/494]: app/Console/Commands/ExpireSuspensionsCommand.php
│ LANGUAGE: php | LINES: 23 | SIZE: 576 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	declare(strict_types=1);
     4	
     5	namespace App\Console\Commands;
     6	
     7	use App\Services\Disciplinary\SuspensionService;
     8	use Illuminate\Console\Command;
     9	
    10	class ExpireSuspensionsCommand extends Command
    11	{
    12	    protected $signature = 'disciplinary:expire-suspensions';
    13	
    14	    protected $description = 'Auto-expire suspensions past their end date and restore member status';
    15	
    16	    public function handle(SuspensionService $service): int
    17	    {
    18	        $count = $service->autoExpireSuspensions();
    19	
    20	        $this->info("Expired {$count} suspension(s).");
    21	
    22	        return self::SUCCESS;
    23	    }
    24	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [015]: app/Console/Commands/ExpireSuspensionsCommand.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [016/494]: app/Console/Commands/GenerateAnnualSubscriptions.php
│ LANGUAGE: php | LINES: 31 | SIZE: 1005 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Console\Commands;
     4	
     5	use App\Services\Financial\SubscriptionService;
     6	use Illuminate\Console\Command;
     7	
     8	class GenerateAnnualSubscriptions extends Command
     9	{
    10	    protected $signature = 'subscriptions:generate {year?}';
    11	    protected $description = 'Generate annual subscriptions for all active members';
    12	
    13	    public function handle(SubscriptionService $service): int
    14	    {
    15	        $year = (int) ($this->argument('year') ?? now()->year);
    16	
    17	        $this->info("Generating subscriptions for year {$year}...");
    18	
    19	        $result = $service->generateBulkSubscriptions($year);
    20	
    21	        $this->info("Generated: {$result['generated']} / Total eligible: {$result['total_members']}");
    22	
    23	        if (count($result['errors']) > 0) {
    24	            $this->warn("Errors: " . count($result['errors']));
    25	            foreach ($result['errors'] as $error) {
    26	                $this->error("  - Member {$error['membership_number']}: {$error['error']}");
    27	            }
    28	        }
    29	
    30	        return self::SUCCESS;
    31	    }
    32	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [016]: app/Console/Commands/GenerateAnnualSubscriptions.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [017/494]: app/Console/Commands/GenerateSubscriptions.php
│ LANGUAGE: php | LINES: 42 | SIZE: 1462 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Console\Commands;
     4	
     5	use App\Models\SubscriptionPeriod;
     6	use App\Services\Subscription\RenewalService;
     7	use Illuminate\Console\Command;
     8	
     9	class GenerateSubscriptions extends Command
    10	{
    11	    protected $signature = 'subscriptions:generate {period? : Period ID to generate for}';
    12	    protected $description = 'Generate subscriptions for all active members in a period';
    13	
    14	    public function handle(RenewalService $renewalService): int
    15	    {
    16	        $periodId = $this->argument('period');
    17	
    18	        $period = $periodId
    19	            ? SubscriptionPeriod::findOrFail($periodId)
    20	            : SubscriptionPeriod::active()->where('auto_generate', true)->first();
    21	
    22	        if (!$period) {
    23	            $this->warn('لا توجد فترة اشتراك نشطة مع الإنشاء التلقائي.');
    24	            return self::SUCCESS;
    25	        }
    26	
    27	        $this->info("Generating subscriptions for: {$period->year} ({$period->name_ar})");
    28	
    29	        $results = $renewalService->bulkGenerate($period);
    30	
    31	        $this->info("Created: {$results['created']}");
    32	        $this->info("Skipped (already exist): {$results['skipped']}");
    33	
    34	        if (count($results['errors']) > 0) {
    35	            $this->warn("Errors: " . count($results['errors']));
    36	            foreach ($results['errors'] as $error) {
    37	                $this->error("  Member #{$error['member_id']} ({$error['member_name']}): {$error['error']}");
    38	            }
    39	        }
    40	
    41	        return self::SUCCESS;
    42	    }
    43	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [017]: app/Console/Commands/GenerateSubscriptions.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [018/494]: app/Console/Commands/MarkOverdueInstallments.php
│ LANGUAGE: php | LINES: 19 | SIZE: 542 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Console\Commands;
     4	
     5	use App\Services\Financial\InstallmentService;
     6	use Illuminate\Console\Command;
     7	
     8	class MarkOverdueInstallments extends Command
     9	{
    10	    protected $signature = 'installments:mark-overdue';
    11	    protected $description = 'Mark installments that have passed their due date as overdue';
    12	
    13	    public function handle(InstallmentService $service): int
    14	    {
    15	        $count = $service->markOverdueInstallments();
    16	        $this->info("Marked {$count} installments as overdue.");
    17	
    18	        return self::SUCCESS;
    19	    }
    20	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [018]: app/Console/Commands/MarkOverdueInstallments.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [019/494]: app/Console/Commands/MarkOverdueSubscriptions.php
│ LANGUAGE: php | LINES: 24 | SIZE: 718 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Console\Commands;
     4	
     5	use App\Models\SubscriptionPeriod;
     6	use App\Services\Subscription\RenewalService;
     7	use Illuminate\Console\Command;
     8	
     9	class MarkOverdueSubscriptions extends Command
    10	{
    11	    protected $signature = 'subscriptions:mark-overdue';
    12	    protected $description = 'Mark pending subscriptions as overdue past grace period';
    13	
    14	    public function handle(RenewalService $renewalService): int
    15	    {
    16	        $periods = SubscriptionPeriod::active()->get();
    17	
    18	        foreach ($periods as $period) {
    19	            $count = $renewalService->markOverdue($period);
    20	            $this->info("Period {$period->year}: Marked {$count} subscriptions as overdue.");
    21	        }
    22	
    23	        return self::SUCCESS;
    24	    }
    25	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [019]: app/Console/Commands/MarkOverdueSubscriptions.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [020/494]: app/Console/Commands/MemberStatsCacheCommand.php
│ LANGUAGE: php | LINES: 49 | SIZE: 2122 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Console\Commands;
     4	
     5	use App\Enums\MembershipStatus;
     6	use App\Models\Member;
     7	use Illuminate\Console\Command;
     8	use Illuminate\Support\Facades\Cache;
     9	
    10	class MemberStatsCacheCommand extends Command
    11	{
    12	    protected $signature = 'members:cache-stats';
    13	
    14	    protected $description = 'تحديث ذاكرة التخزين المؤقت لإحصائيات الأعضاء';
    15	
    16	    public function handle(): int
    17	    {
    18	        $stats = [
    19	            'total' => Member::count(),
    20	            'active' => Member::where('membership_status', MembershipStatus::ACTIVE)->count(),
    21	            'pending' => Member::where('membership_status', MembershipStatus::PENDING)->count(),
    22	            'suspended' => Member::where('membership_status', MembershipStatus::SUSPENDED)->count(),
    23	            'frozen' => Member::where('membership_status', MembershipStatus::FROZEN)->count(),
    24	            'cancelled' => Member::where('membership_status', MembershipStatus::CANCELLED)->count(),
    25	            'deceased' => Member::where('membership_status', MembershipStatus::DECEASED)->count(),
    26	            'by_stage' => Member::where('membership_status', MembershipStatus::PENDING)
    27	                ->selectRaw('workflow_stage, COUNT(*) as count')
    28	                ->groupBy('workflow_stage')
    29	                ->pluck('count', 'workflow_stage')
    30	                ->toArray(),
    31	            'new_this_month' => Member::whereMonth('created_at', now()->month)
    32	                ->whereYear('created_at', now()->year)
    33	                ->count(),
    34	            'activated_this_month' => Member::where('membership_status', MembershipStatus::ACTIVE)
    35	                ->whereMonth('activation_date', now()->month)
    36	                ->whereYear('activation_date', now()->year)
    37	                ->count(),
    38	        ];
    39	
    40	        Cache::put('member_stats', $stats, now()->addMinutes(30));
    41	
    42	        $this->info('تم تحديث إحصائيات الأعضاء');
    43	        $this->table(
    44	            ['المؤشر', 'القيمة'],
    45	            collect($stats)->except('by_stage')->map(fn($v, $k) => [$k, $v])->values()->toArray()
    46	        );
    47	
    48	        return self::SUCCESS;
    49	    }
    50	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [020]: app/Console/Commands/MemberStatsCacheCommand.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [021/494]: app/Console/Commands/PurgeAuditLogs.php
│ LANGUAGE: php | LINES: 27 | SIZE: 805 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Console\Commands;
     4	
     5	use App\Services\Admin\AuditService;
     6	use App\Services\Admin\SettingsService;
     7	use Illuminate\Console\Command;
     8	
     9	class PurgeAuditLogs extends Command
    10	{
    11	    protected $signature = 'admin:purge-audit-logs {--days= : Number of days to retain}';
    12	
    13	    protected $description = 'تنظيف سجلات النشاط القديمة';
    14	
    15	    public function handle(AuditService $auditService, SettingsService $settingsService): int
    16	    {
    17	        $days = $this->option('days')
    18	            ?? $settingsService->get('system.audit_retention_days', 1825);
    19	
    20	        $this->info("حذف سجلات أقدم من {$days} يوم...");
    21	
    22	        $count = $auditService->purgeOlderThan((int) $days);
    23	
    24	        $this->info("تم حذف {$count} سجل.");
    25	
    26	        return self::SUCCESS;
    27	    }
    28	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [021]: app/Console/Commands/PurgeAuditLogs.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [022/494]: app/Console/Commands/ResetFiscalYearSequences.php
│ LANGUAGE: php | LINES: 26 | SIZE: 847 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Console\Commands;
     4	
     5	use App\Services\Admin\SequenceService;
     6	use Illuminate\Console\Command;
     7	
     8	class ResetFiscalYearSequences extends Command
     9	{
    10	    protected $signature = 'admin:reset-sequences {--force : Skip confirmation}';
    11	
    12	    protected $description = 'إعادة تعيين تسلسلات السنة المالية';
    13	
    14	    public function handle(SequenceService $sequenceService): int
    15	    {
    16	        if (!$this->option('force') && !$this->confirm('هل أنت متأكد من إعادة تعيين جميع تسلسلات السنة المالية؟')) {
    17	            $this->info('تم الإلغاء.');
    18	            return self::SUCCESS;
    19	        }
    20	
    21	        $count = $sequenceService->resetAllFiscalYearSequences();
    22	
    23	        $this->info("تم إعادة تعيين {$count} تسلسل.");
    24	
    25	        return self::SUCCESS;
    26	    }
    27	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [022]: app/Console/Commands/ResetFiscalYearSequences.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [023/494]: app/Console/Commands/ScheduleAdmin.php
│ LANGUAGE: php | LINES: 6 | SIZE: 278 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	// Add to app/Console/Kernel.php or bootstrap/app.php schedule:
     4	
     5	// $schedule->command('admin:backup')->dailyAt('02:00');
     6	// $schedule->command('admin:clean-backups --keep=30')->weeklyOn(0, '03:00');
     7	// $schedule->command('admin:purge-audit-logs')->monthlyOn(1, '04:00');│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [023]: app/Console/Commands/ScheduleAdmin.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [024/494]: app/Console/Commands/SendPaymentRemindersCommand.php
│ LANGUAGE: php | LINES: 58 | SIZE: 2053 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Console\Commands;
     4	
     5	use App\Enums\MembershipStatus;
     6	use App\Models\Member;
     7	use Filament\Notifications\Notification;
     8	use Illuminate\Console\Command;
     9	use Illuminate\Support\Carbon;
    10	
    11	class SendPaymentRemindersCommand extends Command
    12	{
    13	    protected $signature = 'members:payment-reminders';
    14	
    15	    protected $description = 'إرسال تنبيهات السداد للأعضاء في المرحلة 7 (أيام 10، 13، 14، 15)';
    16	
    17	    public function handle(): int
    18	    {
    19	        $reminderDays = [10, 13, 14, 15];
    20	
    21	        $members = Member::where('workflow_stage', 7)
    22	            ->where('membership_status', MembershipStatus::PENDING)
    23	            ->whereNotNull('approval_date')
    24	            ->get();
    25	
    26	        $sentCount = 0;
    27	
    28	        foreach ($members as $member) {
    29	            $daysSinceApproval = Carbon::parse($member->approval_date)->diffInDays(now());
    30	
    31	            if (in_array($daysSinceApproval, $reminderDays)) {
    32	                $remaining = 15 - $daysSinceApproval;
    33	
    34	                // Database notification for admins
    35	                $recipients = \App\Models\User::role(['super_admin', 'finance_manager', 'membership_manager'])->get();
    36	
    37	                Notification::make()
    38	                    ->title("⚠️ تنبيه سداد: {$member->full_name_ar}")
    39	                    ->body("متبقي {$remaining} يوم على انتهاء مهلة السداد — تاريخ الموافقة: {$member->approval_date->format('Y-m-d')}")
    40	                    ->warning()
    41	                    ->sendToDatabase($recipients);
    42	
    43	                activity()
    44	                    ->performedOn($member)
    45	                    ->withProperties([
    46	                        'days_since_approval' => $daysSinceApproval,
    47	                        'days_remaining' => $remaining,
    48	                    ])
    49	                    ->log("تنبيه سداد: متبقي {$remaining} يوم");
    50	
    51	                $sentCount++;
    52	            }
    53	        }
    54	
    55	        $this->info("تم إرسال {$sentCount} تنبيه سداد");
    56	
    57	        return self::SUCCESS;
    58	    }
    59	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [024]: app/Console/Commands/SendPaymentRemindersCommand.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [025/494]: app/Console/Kernel.php
│ LANGUAGE: php | LINES: 34 | SIZE: 1100 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Console;
     4	
     5	use Illuminate\Console\Scheduling\Schedule;
     6	use Illuminate\Foundation\Console\Kernel as ConsoleKernel;
     7	
     8	class Kernel extends ConsoleKernel
     9	{
    10	    protected function schedule(Schedule $schedule): void
    11	    {
    12	        // Mark overdue subscriptions daily at midnight
    13	        $schedule->command('subscriptions:mark-overdue')
    14	            ->dailyAt('00:30')
    15	            ->withoutOverlapping()
    16	            ->appendOutputTo(storage_path('logs/overdue-subscriptions.log'));
    17	
    18	        // Mark overdue installments daily
    19	        $schedule->command('installments:mark-overdue')
    20	            ->dailyAt('00:45')
    21	            ->withoutOverlapping()
    22	            ->appendOutputTo(storage_path('logs/overdue-installments.log'));
    23	
    24	        // Generate subscriptions for new year on Jan 1
    25	        $schedule->command('subscriptions:generate')
    26	            ->yearlyOn(1, 1, '01:00')
    27	            ->withoutOverlapping()
    28	            ->appendOutputTo(storage_path('logs/annual-subscriptions.log'));
    29	    }
    30	
    31	    protected function commands(): void
    32	    {
    33	        $this->load(__DIR__ . '/Commands');
    34	    }
    35	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [025]: app/Console/Kernel.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [026/494]: app/DTOs/FeeCalculationResult.php
│ LANGUAGE: php | LINES: 24 | SIZE: 610 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\DTOs;
     4	
     5	class FeeCalculationResult
     6	{
     7	    public function __construct(
     8	        public readonly float $baseAmount,
     9	        public readonly float $discountAmount,
    10	        public readonly float $lateFeeAmount,
    11	        public readonly float $totalAmount,
    12	        public readonly array $breakdown = [],
    13	        public readonly ?string $notes = null,
    14	    ) {}
    15	
    16	    public static function simple(float $amount): static
    17	    {
    18	        return new static(
    19	            baseAmount: $amount,
    20	            discountAmount: 0,
    21	            lateFeeAmount: 0,
    22	            totalAmount: $amount,
    23	        );
    24	    }
    25	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [026]: app/DTOs/FeeCalculationResult.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [027/494]: app/DTOs/MemberData.php
│ LANGUAGE: php | LINES: 57 | SIZE: 2617 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\DTOs;
     4	
     5	class MemberData
     6	{
     7	    public function __construct(
     8	        public readonly string $full_name_ar,
     9	        public readonly string $phone_primary,
    10	        public readonly int $membership_type_id,
    11	        public readonly ?string $full_name_en = null,
    12	        public readonly ?string $national_id = null,
    13	        public readonly ?string $passport_number = null,
    14	        public readonly ?string $gender = null,
    15	        public readonly ?string $date_of_birth = null,
    16	        public readonly ?int $nationality_id = null,
    17	        public readonly ?int $religion_id = null,
    18	        public readonly ?int $marital_status_id = null,
    19	        public readonly ?int $birth_governorate_id = null,
    20	        public readonly ?string $job_title = null,
    21	        public readonly ?string $employer_name = null,
    22	        public readonly ?int $educational_qualification_id = null,
    23	        public readonly ?string $phone_secondary = null,
    24	        public readonly ?string $email = null,
    25	        public readonly ?int $address_governorate_id = null,
    26	        public readonly ?string $address_city = null,
    27	        public readonly ?string $address_street = null,
    28	        public readonly ?string $address_postal_code = null,
    29	        public readonly ?string $full_address_ar = null,
    30	        public readonly ?string $full_address_en = null,
    31	        public readonly ?string $application_form_number = null,
    32	        public readonly ?string $application_form_date = null,
    33	        public readonly ?int $sponsor_member_1_id = null,
    34	        public readonly ?int $sponsor_member_2_id = null,
    35	        public readonly ?string $referral_source = null,
    36	        public readonly ?string $notes = null,
    37	    ) {}
    38	
    39	    public static function fromArray(array $data): static
    40	    {
    41	        return new static(...collect($data)->only([
    42	            'full_name_ar', 'phone_primary', 'membership_type_id',
    43	            'full_name_en', 'national_id', 'passport_number', 'gender',
    44	            'date_of_birth', 'nationality_id', 'religion_id', 'marital_status_id',
    45	            'birth_governorate_id', 'job_title', 'employer_name',
    46	            'educational_qualification_id', 'phone_secondary', 'email',
    47	            'address_governorate_id', 'address_city', 'address_street',
    48	            'address_postal_code', 'full_address_ar', 'full_address_en',
    49	            'application_form_number', 'application_form_date',
    50	            'sponsor_member_1_id', 'sponsor_member_2_id', 'referral_source', 'notes',
    51	        ])->toArray());
    52	    }
    53	
    54	    public function toArray(): array
    55	    {
    56	        return array_filter(get_object_vars($this), fn ($v) => $v !== null);
    57	    }
    58	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [027]: app/DTOs/MemberData.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [028/494]: app/DTOs/ReceiptData.php
│ LANGUAGE: php | LINES: 39 | SIZE: 1396 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\DTOs;
     4	
     5	class ReceiptData
     6	{
     7	    public function __construct(
     8	        public readonly int $member_id,
     9	        public readonly string $fee_category,
    10	        public readonly float $total_amount,
    11	        public readonly string $payment_method,
    12	        public readonly ?int $fiscal_year_id = null,
    13	        public readonly ?int $cash_register_id = null,
    14	        public readonly ?string $notes = null,
    15	        public readonly array $items = [],
    16	        public readonly ?float $discount_amount = null,
    17	        public readonly ?string $discount_reason = null,
    18	    ) {}
    19	
    20	    public static function fromArray(array $data): static
    21	    {
    22	        return new static(
    23	            member_id: $data['member_id'],
    24	            fee_category: $data['fee_category'],
    25	            total_amount: (float) $data['total_amount'],
    26	            payment_method: $data['payment_method'],
    27	            fiscal_year_id: $data['fiscal_year_id'] ?? null,
    28	            cash_register_id: $data['cash_register_id'] ?? null,
    29	            notes: $data['notes'] ?? null,
    30	            items: $data['items'] ?? [],
    31	            discount_amount: isset($data['discount_amount']) ? (float) $data['discount_amount'] : null,
    32	            discount_reason: $data['discount_reason'] ?? null,
    33	        );
    34	    }
    35	
    36	    public function toArray(): array
    37	    {
    38	        return array_filter(get_object_vars($this), fn ($v) => $v !== null);
    39	    }
    40	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [028]: app/DTOs/ReceiptData.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [029/494]: app/Enums/AuditAction.php
│ LANGUAGE: php | LINES: 107 | SIZE: 3708 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Enums;
     4	
     5	enum AuditAction: string
     6	{
     7	    case Created = 'created';
     8	    case Updated = 'updated';
     9	    case Deleted = 'deleted';
    10	    case Restored = 'restored';
    11	    case Archived = 'archived';
    12	    case StatusChanged = 'status_changed';
    13	    case Login = 'login';
    14	    case Logout = 'logout';
    15	    case FailedLogin = 'failed_login';
    16	    case Exported = 'exported';
    17	    case Imported = 'imported';
    18	    case Printed = 'printed';
    19	    case Approved = 'approved';
    20	    case Rejected = 'rejected';
    21	
    22	    public function getLabel(): string
    23	    {
    24	        return match ($this) {
    25	            self::Created => 'إنشاء',
    26	            self::Updated => 'تعديل',
    27	            self::Deleted => 'حذف',
    28	            self::Restored => 'استرجاع',
    29	            self::Archived => 'أرشفة',
    30	            self::StatusChanged => 'تغيير حالة',
    31	            self::Login => 'تسجيل دخول',
    32	            self::Logout => 'تسجيل خروج',
    33	            self::FailedLogin => 'محاولة دخول فاشلة',
    34	            self::Exported => 'تصدير',
    35	            self::Imported => 'استيراد',
    36	            self::Printed => 'طباعة',
    37	            self::Approved => 'موافقة',
    38	            self::Rejected => 'رفض',
    39	        };
    40	    }
    41	
    42	    public function getLabelEn(): string
    43	    {
    44	        return match ($this) {
    45	            self::Created => 'Created',
    46	            self::Updated => 'Updated',
    47	            self::Deleted => 'Deleted',
    48	            self::Restored => 'Restored',
    49	            self::Archived => 'Archived',
    50	            self::StatusChanged => 'Status Changed',
    51	            self::Login => 'Login',
    52	            self::Logout => 'Logout',
    53	            self::FailedLogin => 'Failed Login',
    54	            self::Exported => 'Exported',
    55	            self::Imported => 'Imported',
    56	            self::Printed => 'Printed',
    57	            self::Approved => 'Approved',
    58	            self::Rejected => 'Rejected',
    59	        };
    60	    }
    61	
    62	    public function getColor(): string
    63	    {
    64	        return match ($this) {
    65	            self::Created => 'success',
    66	            self::Updated => 'info',
    67	            self::Deleted => 'danger',
    68	            self::Restored => 'success',
    69	            self::Archived => 'gray',
    70	            self::StatusChanged => 'warning',
    71	            self::Login => 'success',
    72	            self::Logout => 'gray',
    73	            self::FailedLogin => 'danger',
    74	            self::Exported => 'info',
    75	            self::Imported => 'info',
    76	            self::Printed => 'gray',
    77	            self::Approved => 'success',
    78	            self::Rejected => 'danger',
    79	        };
    80	    }
    81	
    82	    public function getIcon(): string
    83	    {
    84	        return match ($this) {
    85	            self::Created => 'heroicon-o-plus-circle',
    86	            self::Updated => 'heroicon-o-pencil',
    87	            self::Deleted => 'heroicon-o-trash',
    88	            self::Restored => 'heroicon-o-arrow-uturn-left',
    89	            self::Archived => 'heroicon-o-archive-box',
    90	            self::StatusChanged => 'heroicon-o-arrow-path',
    91	            self::Login => 'heroicon-o-arrow-right-on-rectangle',
    92	            self::Logout => 'heroicon-o-arrow-left-on-rectangle',
    93	            self::FailedLogin => 'heroicon-o-shield-exclamation',
    94	            self::Exported => 'heroicon-o-arrow-down-tray',
    95	            self::Imported => 'heroicon-o-arrow-up-tray',
    96	            self::Printed => 'heroicon-o-printer',
    97	            self::Approved => 'heroicon-o-check-circle',
    98	            self::Rejected => 'heroicon-o-x-circle',
    99	        };
   100	    }
   101	
   102	    public static function toFilamentOptions(): array
   103	    {
   104	        return collect(self::cases())
   105	            ->mapWithKeys(fn (self $case) => [$case->value => $case->getLabel()])
   106	            ->toArray();
   107	    }
   108	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [029]: app/Enums/AuditAction.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [030/494]: app/Enums/BoardDecisionResult.php
│ LANGUAGE: php | LINES: 62 | SIZE: 1835 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Enums;
     4	
     5	enum BoardDecisionResult: string
     6	{
     7	    case Approved = 'approved';
     8	    case Rejected = 'rejected';
     9	    case Deferred = 'deferred';
    10	    case ConditionallyApproved = 'conditionally_approved';
    11	    case Tabled = 'tabled';
    12	
    13	    public function getLabel(): string
    14	    {
    15	        return match ($this) {
    16	            self::Approved => 'موافق',
    17	            self::Rejected => 'مرفوض',
    18	            self::Deferred => 'مؤجل',
    19	            self::ConditionallyApproved => 'موافق مشروط',
    20	            self::Tabled => 'مؤجل للدراسة',
    21	        };
    22	    }
    23	
    24	    public function getLabelEn(): string
    25	    {
    26	        return match ($this) {
    27	            self::Approved => 'Approved',
    28	            self::Rejected => 'Rejected',
    29	            self::Deferred => 'Deferred',
    30	            self::ConditionallyApproved => 'Conditionally Approved',
    31	            self::Tabled => 'Tabled',
    32	        };
    33	    }
    34	
    35	    public function getColor(): string
    36	    {
    37	        return match ($this) {
    38	            self::Approved => 'success',
    39	            self::Rejected => 'danger',
    40	            self::Deferred => 'warning',
    41	            self::ConditionallyApproved => 'info',
    42	            self::Tabled => 'gray',
    43	        };
    44	    }
    45	
    46	    public function getIcon(): string
    47	    {
    48	        return match ($this) {
    49	            self::Approved => 'heroicon-o-hand-thumb-up',
    50	            self::Rejected => 'heroicon-o-hand-thumb-down',
    51	            self::Deferred => 'heroicon-o-clock',
    52	            self::ConditionallyApproved => 'heroicon-o-question-mark-circle',
    53	            self::Tabled => 'heroicon-o-document-minus',
    54	        };
    55	    }
    56	
    57	    public static function toFilamentOptions(): array
    58	    {
    59	        return collect(self::cases())
    60	            ->mapWithKeys(fn (self $case) => [$case->value => $case->getLabel()])
    61	            ->toArray();
    62	    }
    63	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [030]: app/Enums/BoardDecisionResult.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [031/494]: app/Enums/BoardDecisionType.php
│ LANGUAGE: php | LINES: 97 | SIZE: 3743 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Enums;
     4	
     5	enum BoardDecisionType: string
     6	{
     7	    case MembershipApproval = 'membership_approval';
     8	    case MembershipRejection = 'membership_rejection';
     9	    case Suspension = 'suspension';
    10	    case Expulsion = 'expulsion';
    11	    case Reinstatement = 'reinstatement';
    12	    case Transfer = 'transfer';
    13	    case FeeAdjustment = 'fee_adjustment';
    14	    case PolicyChange = 'policy_change';
    15	    case PenaltyImposition = 'penalty_imposition';
    16	    case AppealDecision = 'appeal_decision';
    17	    case HonoraryMembership = 'honorary_membership';
    18	    case Other = 'other';
    19	
    20	    public function getLabel(): string
    21	    {
    22	        return match ($this) {
    23	            self::MembershipApproval => 'موافقة على عضوية',
    24	            self::MembershipRejection => 'رفض عضوية',
    25	            self::Suspension => 'إيقاف',
    26	            self::Expulsion => 'فصل',
    27	            self::Reinstatement => 'إعادة عضوية',
    28	            self::Transfer => 'نقل',
    29	            self::FeeAdjustment => 'تعديل رسوم',
    30	            self::PolicyChange => 'تغيير سياسة',
    31	            self::PenaltyImposition => 'فرض عقوبة',
    32	            self::AppealDecision => 'قرار استئناف',
    33	            self::HonoraryMembership => 'عضوية فخرية',
    34	            self::Other => 'أخرى',
    35	        };
    36	    }
    37	
    38	    public function getLabelEn(): string
    39	    {
    40	        return match ($this) {
    41	            self::MembershipApproval => 'Membership Approval',
    42	            self::MembershipRejection => 'Membership Rejection',
    43	            self::Suspension => 'Suspension',
    44	            self::Expulsion => 'Expulsion',
    45	            self::Reinstatement => 'Reinstatement',
    46	            self::Transfer => 'Transfer',
    47	            self::FeeAdjustment => 'Fee Adjustment',
    48	            self::PolicyChange => 'Policy Change',
    49	            self::PenaltyImposition => 'Penalty Imposition',
    50	            self::AppealDecision => 'Appeal Decision',
    51	            self::HonoraryMembership => 'Honorary Membership',
    52	            self::Other => 'Other',
    53	        };
    54	    }
    55	
    56	    public function getColor(): string
    57	    {
    58	        return match ($this) {
    59	            self::MembershipApproval => 'success',
    60	            self::MembershipRejection => 'danger',
    61	            self::Suspension => 'warning',
    62	            self::Expulsion => 'danger',
    63	            self::Reinstatement => 'success',
    64	            self::Transfer => 'info',
    65	            self::FeeAdjustment => 'warning',
    66	            self::PolicyChange => 'info',
    67	            self::PenaltyImposition => 'danger',
    68	            self::AppealDecision => 'info',
    69	            self::HonoraryMembership => 'primary',
    70	            self::Other => 'gray',
    71	        };
    72	    }
    73	
    74	    public function getIcon(): string
    75	    {
    76	        return match ($this) {
    77	            self::MembershipApproval => 'heroicon-o-check-circle',
    78	            self::MembershipRejection => 'heroicon-o-x-circle',
    79	            self::Suspension => 'heroicon-o-pause-circle',
    80	            self::Expulsion => 'heroicon-o-x-mark',
    81	            self::Reinstatement => 'heroicon-o-arrow-uturn-left',
    82	            self::Transfer => 'heroicon-o-arrows-right-left',
    83	            self::FeeAdjustment => 'heroicon-o-currency-dollar',
    84	            self::PolicyChange => 'heroicon-o-document-text',
    85	            self::PenaltyImposition => 'heroicon-o-exclamation-circle',
    86	            self::AppealDecision => 'heroicon-o-scale',
    87	            self::HonoraryMembership => 'heroicon-o-star',
    88	            self::Other => 'heroicon-o-ellipsis-horizontal',
    89	        };
    90	    }
    91	
    92	    public static function toFilamentOptions(): array
    93	    {
    94	        return collect(self::cases())
    95	            ->mapWithKeys(fn (self $case) => [$case->value => $case->getLabel()])
    96	            ->toArray();
    97	    }
    98	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [031]: app/Enums/BoardDecisionType.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [032/494]: app/Enums/BoardOfferStatus.php
│ LANGUAGE: php | LINES: 67 | SIZE: 1997 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Enums;
     4	
     5	enum BoardOfferStatus: string
     6	{
     7	    case Draft = 'draft';
     8	    case Submitted = 'submitted';
     9	    case OnAgenda = 'on_agenda';
    10	    case Discussed = 'discussed';
    11	    case Decided = 'decided';
    12	    case Withdrawn = 'withdrawn';
    13	
    14	    public function getLabel(): string
    15	    {
    16	        return match ($this) {
    17	            self::Draft => 'مسودة',
    18	            self::Submitted => 'مقدم',
    19	            self::OnAgenda => 'على جدول الأعمال',
    20	            self::Discussed => 'تمت مناقشته',
    21	            self::Decided => 'تم البت فيه',
    22	            self::Withdrawn => 'مسحوب',
    23	        };
    24	    }
    25	
    26	    public function getLabelEn(): string
    27	    {
    28	        return match ($this) {
    29	            self::Draft => 'Draft',
    30	            self::Submitted => 'Submitted',
    31	            self::OnAgenda => 'On Agenda',
    32	            self::Discussed => 'Discussed',
    33	            self::Decided => 'Decided',
    34	            self::Withdrawn => 'Withdrawn',
    35	        };
    36	    }
    37	
    38	    public function getColor(): string
    39	    {
    40	        return match ($this) {
    41	            self::Draft => 'gray',
    42	            self::Submitted => 'info',
    43	            self::OnAgenda => 'warning',
    44	            self::Discussed => 'info',
    45	            self::Decided => 'success',
    46	            self::Withdrawn => 'gray',
    47	        };
    48	    }
    49	
    50	    public function getIcon(): string
    51	    {
    52	        return match ($this) {
    53	            self::Draft => 'heroicon-o-pencil-square',
    54	            self::Submitted => 'heroicon-o-paper-airplane',
    55	            self::OnAgenda => 'heroicon-o-clipboard-document-list',
    56	            self::Discussed => 'heroicon-o-chat-bubble-left-right',
    57	            self::Decided => 'heroicon-o-check-circle',
    58	            self::Withdrawn => 'heroicon-o-arrow-uturn-left',
    59	        };
    60	    }
    61	
    62	    public static function toFilamentOptions(): array
    63	    {
    64	        return collect(self::cases())
    65	            ->mapWithKeys(fn (self $case) => [$case->value => $case->getLabel()])
    66	            ->toArray();
    67	    }
    68	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [032]: app/Enums/BoardOfferStatus.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [033/494]: app/Enums/BookingStatus.php
│ LANGUAGE: php | LINES: 67 | SIZE: 1931 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Enums;
     4	
     5	enum BookingStatus: string
     6	{
     7	    case Pending = 'pending';
     8	    case Confirmed = 'confirmed';
     9	    case CheckedIn = 'checked_in';
    10	    case Completed = 'completed';
    11	    case Cancelled = 'cancelled';
    12	    case NoShow = 'no_show';
    13	
    14	    public function getLabel(): string
    15	    {
    16	        return match ($this) {
    17	            self::Pending => 'معلق',
    18	            self::Confirmed => 'مؤكد',
    19	            self::CheckedIn => 'تم الحضور',
    20	            self::Completed => 'مكتمل',
    21	            self::Cancelled => 'ملغي',
    22	            self::NoShow => 'لم يحضر',
    23	        };
    24	    }
    25	
    26	    public function getLabelEn(): string
    27	    {
    28	        return match ($this) {
    29	            self::Pending => 'Pending',
    30	            self::Confirmed => 'Confirmed',
    31	            self::CheckedIn => 'Checked In',
    32	            self::Completed => 'Completed',
    33	            self::Cancelled => 'Cancelled',
    34	            self::NoShow => 'No Show',
    35	        };
    36	    }
    37	
    38	    public function getColor(): string
    39	    {
    40	        return match ($this) {
    41	            self::Pending => 'warning',
    42	            self::Confirmed => 'info',
    43	            self::CheckedIn => 'success',
    44	            self::Completed => 'success',
    45	            self::Cancelled => 'gray',
    46	            self::NoShow => 'danger',
    47	        };
    48	    }
    49	
    50	    public function getIcon(): string
    51	    {
    52	        return match ($this) {
    53	            self::Pending => 'heroicon-o-clock',
    54	            self::Confirmed => 'heroicon-o-check',
    55	            self::CheckedIn => 'heroicon-o-check-circle',
    56	            self::Completed => 'heroicon-o-check-badge',
    57	            self::Cancelled => 'heroicon-o-x-circle',
    58	            self::NoShow => 'heroicon-o-user-minus',
    59	        };
    60	    }
    61	
    62	    public static function toFilamentOptions(): array
    63	    {
    64	        return collect(self::cases())
    65	            ->mapWithKeys(fn (self $case) => [$case->value => $case->getLabel()])
    66	            ->toArray();
    67	    }
    68	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [033]: app/Enums/BookingStatus.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [034/494]: app/Enums/CardStatus.php
│ LANGUAGE: php | LINES: 87 | SIZE: 2708 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Enums;
     4	
     5	enum CardStatus: string
     6	{
     7	    case Requested = 'requested';
     8	    case Printing = 'printing';
     9	    case Printed = 'printed';
    10	    case Issued = 'issued';
    11	    case Active = 'active';
    12	    case Expired = 'expired';
    13	    case Lost = 'lost';
    14	    case Damaged = 'damaged';
    15	    case Revoked = 'revoked';
    16	    case Replaced = 'replaced';
    17	
    18	    public function getLabel(): string
    19	    {
    20	        return match ($this) {
    21	            self::Requested => 'مطلوب',
    22	            self::Printing => 'قيد الطباعة',
    23	            self::Printed => 'مطبوع',
    24	            self::Issued => 'صادر',
    25	            self::Active => 'نشط',
    26	            self::Expired => 'منتهي',
    27	            self::Lost => 'مفقود',
    28	            self::Damaged => 'تالف',
    29	            self::Revoked => 'ملغي',
    30	            self::Replaced => 'مستبدل',
    31	        };
    32	    }
    33	
    34	    public function getLabelEn(): string
    35	    {
    36	        return match ($this) {
    37	            self::Requested => 'Requested',
    38	            self::Printing => 'Printing',
    39	            self::Printed => 'Printed',
    40	            self::Issued => 'Issued',
    41	            self::Active => 'Active',
    42	            self::Expired => 'Expired',
    43	            self::Lost => 'Lost',
    44	            self::Damaged => 'Damaged',
    45	            self::Revoked => 'Revoked',
    46	            self::Replaced => 'Replaced',
    47	        };
    48	    }
    49	
    50	    public function getColor(): string
    51	    {
    52	        return match ($this) {
    53	            self::Requested => 'info',
    54	            self::Printing => 'warning',
    55	            self::Printed => 'info',
    56	            self::Issued => 'success',
    57	            self::Active => 'success',
    58	            self::Expired => 'gray',
    59	            self::Lost => 'danger',
    60	            self::Damaged => 'danger',
    61	            self::Revoked => 'danger',
    62	            self::Replaced => 'gray',
    63	        };
    64	    }
    65	
    66	    public function getIcon(): string
    67	    {
    68	        return match ($this) {
    69	            self::Requested => 'heroicon-o-credit-card',
    70	            self::Printing => 'heroicon-o-printer',
    71	            self::Printed => 'heroicon-o-check',
    72	            self::Issued => 'heroicon-o-identification',
    73	            self::Active => 'heroicon-o-check-circle',
    74	            self::Expired => 'heroicon-o-clock',
    75	            self::Lost => 'heroicon-o-question-mark-circle',
    76	            self::Damaged => 'heroicon-o-exclamation-triangle',
    77	            self::Revoked => 'heroicon-o-x-circle',
    78	            self::Replaced => 'heroicon-o-arrow-path',
    79	        };
    80	    }
    81	
    82	    public static function toFilamentOptions(): array
    83	    {
    84	        return collect(self::cases())
    85	            ->mapWithKeys(fn (self $case) => [$case->value => $case->getLabel()])
    86	            ->toArray();
    87	    }
    88	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [034]: app/Enums/CardStatus.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [035/494]: app/Enums/CashRegisterStatus.php
│ LANGUAGE: php | LINES: 52 | SIZE: 1255 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Enums;
     4	
     5	enum CashRegisterStatus: string
     6	{
     7	    case Open = 'open';
     8	    case Closed = 'closed';
     9	    case Suspended = 'suspended';
    10	
    11	    public function getLabel(): string
    12	    {
    13	        return match ($this) {
    14	            self::Open => 'مفتوح',
    15	            self::Closed => 'مغلق',
    16	            self::Suspended => 'معلق',
    17	        };
    18	    }
    19	
    20	    public function getLabelEn(): string
    21	    {
    22	        return match ($this) {
    23	            self::Open => 'Open',
    24	            self::Closed => 'Closed',
    25	            self::Suspended => 'Suspended',
    26	        };
    27	    }
    28	
    29	    public function getColor(): string
    30	    {
    31	        return match ($this) {
    32	            self::Open => 'success',
    33	            self::Closed => 'gray',
    34	            self::Suspended => 'warning',
    35	        };
    36	    }
    37	
    38	    public function getIcon(): string
    39	    {
    40	        return match ($this) {
    41	            self::Open => 'heroicon-o-lock-open',
    42	            self::Closed => 'heroicon-o-lock-closed',
    43	            self::Suspended => 'heroicon-o-pause-circle',
    44	        };
    45	    }
    46	
    47	    public static function toFilamentOptions(): array
    48	    {
    49	        return collect(self::cases())
    50	            ->mapWithKeys(fn (self $case) => [$case->value => $case->getLabel()])
    51	            ->toArray();
    52	    }
    53	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [035]: app/Enums/CashRegisterStatus.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [036/494]: app/Enums/DependentStatus.php
│ LANGUAGE: php | LINES: 67 | SIZE: 1994 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Enums;
     4	
     5	enum DependentStatus: string
     6	{
     7	    case Active = 'active';
     8	    case Inactive = 'inactive';
     9	    case Suspended = 'suspended';
    10	    case Removed = 'removed';
    11	    case AgedOut = 'aged_out';
    12	    case PendingApproval = 'pending_approval';
    13	
    14	    public function getLabel(): string
    15	    {
    16	        return match ($this) {
    17	            self::Active => 'نشط',
    18	            self::Inactive => 'غير نشط',
    19	            self::Suspended => 'موقوف',
    20	            self::Removed => 'محذوف',
    21	            self::AgedOut => 'تجاوز السن',
    22	            self::PendingApproval => 'بانتظار الموافقة',
    23	        };
    24	    }
    25	
    26	    public function getLabelEn(): string
    27	    {
    28	        return match ($this) {
    29	            self::Active => 'Active',
    30	            self::Inactive => 'Inactive',
    31	            self::Suspended => 'Suspended',
    32	            self::Removed => 'Removed',
    33	            self::AgedOut => 'Aged Out',
    34	            self::PendingApproval => 'Pending Approval',
    35	        };
    36	    }
    37	
    38	    public function getColor(): string
    39	    {
    40	        return match ($this) {
    41	            self::Active => 'success',
    42	            self::Inactive => 'gray',
    43	            self::Suspended => 'danger',
    44	            self::Removed => 'gray',
    45	            self::AgedOut => 'warning',
    46	            self::PendingApproval => 'info',
    47	        };
    48	    }
    49	
    50	    public function getIcon(): string
    51	    {
    52	        return match ($this) {
    53	            self::Active => 'heroicon-o-check-circle',
    54	            self::Inactive => 'heroicon-o-minus-circle',
    55	            self::Suspended => 'heroicon-o-pause-circle',
    56	            self::Removed => 'heroicon-o-x-circle',
    57	            self::AgedOut => 'heroicon-o-clock',
    58	            self::PendingApproval => 'heroicon-o-question-mark-circle',
    59	        };
    60	    }
    61	
    62	    public static function toFilamentOptions(): array
    63	    {
    64	        return collect(self::cases())
    65	            ->mapWithKeys(fn (self $case) => [$case->value => $case->getLabel()])
    66	            ->toArray();
    67	    }
    68	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [036]: app/Enums/DependentStatus.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [037/494]: app/Enums/FeeCategory.php
│ LANGUAGE: php | LINES: 115 | SIZE: 4354 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Enums;
     4	
     5	enum FeeCategory: string
     6	{
     7	    case ApplicationForm = 'application_form';
     8	    case MembershipValue = 'membership_value';
     9	    case AnnualSubscription = 'annual_subscription';
    10	    case DependentFee = 'dependent_fee';
    11	    case CardIssuance = 'card_issuance';
    12	    case CardReplacement = 'card_replacement';
    13	    case TransferFee = 'transfer_fee';
    14	    case PenaltyFine = 'penalty_fine';
    15	    case ReinstatementFee = 'reinstatement_fee';
    16	    case FacilityBooking = 'facility_booking';
    17	    case LateFee = 'late_fee';
    18	    case Other = 'other';
    19	
    20	    public function getLabel(): string
    21	    {
    22	        return match ($this) {
    23	            self::ApplicationForm => 'استمارة تقديم',
    24	            self::MembershipValue => 'ثمن العضوية',
    25	            self::AnnualSubscription => 'اشتراك سنوي',
    26	            self::DependentFee => 'رسوم تابع',
    27	            self::CardIssuance => 'إصدار كارنيه',
    28	            self::CardReplacement => 'بدل فاقد كارنيه',
    29	            self::TransferFee => 'رسوم نقل',
    30	            self::PenaltyFine => 'غرامة',
    31	            self::ReinstatementFee => 'رسوم استعادة',
    32	            self::FacilityBooking => 'حجز مرفق',
    33	            self::LateFee => 'رسوم تأخير',
    34	            self::Other => 'أخرى',
    35	        };
    36	    }
    37	
    38	    public function getLabelEn(): string
    39	    {
    40	        return match ($this) {
    41	            self::ApplicationForm => 'Application Form',
    42	            self::MembershipValue => 'Membership Value',
    43	            self::AnnualSubscription => 'Annual Subscription',
    44	            self::DependentFee => 'Dependent Fee',
    45	            self::CardIssuance => 'Card Issuance',
    46	            self::CardReplacement => 'Card Replacement',
    47	            self::TransferFee => 'Transfer Fee',
    48	            self::PenaltyFine => 'Penalty Fine',
    49	            self::ReinstatementFee => 'Reinstatement Fee',
    50	            self::FacilityBooking => 'Facility Booking',
    51	            self::LateFee => 'Late Fee',
    52	            self::Other => 'Other',
    53	        };
    54	    }
    55	
    56	    public function getColor(): string
    57	    {
    58	        return match ($this) {
    59	            self::ApplicationForm => 'info',
    60	            self::MembershipValue => 'primary',
    61	            self::AnnualSubscription => 'success',
    62	            self::DependentFee => 'info',
    63	            self::CardIssuance => 'warning',
    64	            self::CardReplacement => 'warning',
    65	            self::TransferFee => 'info',
    66	            self::PenaltyFine => 'danger',
    67	            self::ReinstatementFee => 'warning',
    68	            self::FacilityBooking => 'success',
    69	            self::LateFee => 'danger',
    70	            self::Other => 'gray',
    71	        };
    72	    }
    73	
    74	    public function getIcon(): string
    75	    {
    76	        return match ($this) {
    77	            self::ApplicationForm => 'heroicon-o-document-text',
    78	            self::MembershipValue => 'heroicon-o-banknotes',
    79	            self::AnnualSubscription => 'heroicon-o-calendar',
    80	            self::DependentFee => 'heroicon-o-user-plus',
    81	            self::CardIssuance => 'heroicon-o-identification',
    82	            self::CardReplacement => 'heroicon-o-arrow-path',
    83	            self::TransferFee => 'heroicon-o-arrows-right-left',
    84	            self::PenaltyFine => 'heroicon-o-exclamation-circle',
    85	            self::ReinstatementFee => 'heroicon-o-arrow-uturn-left',
    86	            self::FacilityBooking => 'heroicon-o-building-office',
    87	            self::LateFee => 'heroicon-o-clock',
    88	            self::Other => 'heroicon-o-ellipsis-horizontal',
    89	        };
    90	    }
    91	
    92	    public function getReceiptPrefix(): string
    93	    {
    94	        return match ($this) {
    95	            self::ApplicationForm => 'APP',
    96	            self::MembershipValue => 'MBV',
    97	            self::AnnualSubscription => 'SUB',
    98	            self::DependentFee => 'DEP',
    99	            self::CardIssuance => 'CRD',
   100	            self::CardReplacement => 'CRD',
   101	            self::TransferFee => 'TRN',
   102	            self::PenaltyFine => 'PEN',
   103	            self::ReinstatementFee => 'RST',
   104	            self::FacilityBooking => 'BKG',
   105	            self::LateFee => 'PEN',
   106	            self::Other => 'GEN',
   107	        };
   108	    }
   109	
   110	    public static function toFilamentOptions(): array
   111	    {
   112	        return collect(self::cases())
   113	            ->mapWithKeys(fn (self $case) => [$case->value => $case->getLabel()])
   114	            ->toArray();
   115	    }
   116	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [037]: app/Enums/FeeCategory.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [038/494]: app/Enums/Gender.php
│ LANGUAGE: php | LINES: 47 | SIZE: 1005 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Enums;
     4	
     5	enum Gender: string
     6	{
     7	    case Male = 'male';
     8	    case Female = 'female';
     9	
    10	    public function getLabel(): string
    11	    {
    12	        return match ($this) {
    13	            self::Male => 'ذكر',
    14	            self::Female => 'أنثى',
    15	        };
    16	    }
    17	
    18	    public function getLabelEn(): string
    19	    {
    20	        return match ($this) {
    21	            self::Male => 'Male',
    22	            self::Female => 'Female',
    23	        };
    24	    }
    25	
    26	    public function getColor(): string
    27	    {
    28	        return match ($this) {
    29	            self::Male => 'info',
    30	            self::Female => 'danger',
    31	        };
    32	    }
    33	
    34	    public function getIcon(): string
    35	    {
    36	        return match ($this) {
    37	            self::Male => 'heroicon-o-user',
    38	            self::Female => 'heroicon-o-user',
    39	        };
    40	    }
    41	
    42	    public static function toFilamentOptions(): array
    43	    {
    44	        return collect(self::cases())
    45	            ->mapWithKeys(fn (self $case) => [$case->value => $case->getLabel()])
    46	            ->toArray();
    47	    }
    48	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [038]: app/Enums/Gender.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [039/494]: app/Enums/GenderRestriction.php
│ LANGUAGE: php | LINES: 52 | SIZE: 1292 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Enums;
     4	
     5	enum GenderRestriction: string
     6	{
     7	    case MaleOnly = 'male_only';
     8	    case FemaleOnly = 'female_only';
     9	    case Mixed = 'mixed';
    10	
    11	    public function getLabel(): string
    12	    {
    13	        return match ($this) {
    14	            self::MaleOnly => 'ذكور فقط',
    15	            self::FemaleOnly => 'إناث فقط',
    16	            self::Mixed => 'مختلط',
    17	        };
    18	    }
    19	
    20	    public function getLabelEn(): string
    21	    {
    22	        return match ($this) {
    23	            self::MaleOnly => 'Male Only',
    24	            self::FemaleOnly => 'Female Only',
    25	            self::Mixed => 'Mixed',
    26	        };
    27	    }
    28	
    29	    public function getColor(): string
    30	    {
    31	        return match ($this) {
    32	            self::MaleOnly => 'info',
    33	            self::FemaleOnly => 'danger',
    34	            self::Mixed => 'success',
    35	        };
    36	    }
    37	
    38	    public function getIcon(): string
    39	    {
    40	        return match ($this) {
    41	            self::MaleOnly => 'heroicon-o-user-group',
    42	            self::FemaleOnly => 'heroicon-o-user-group',
    43	            self::Mixed => 'heroicon-o-users',
    44	        };
    45	    }
    46	
    47	    public static function toFilamentOptions(): array
    48	    {
    49	        return collect(self::cases())
    50	            ->mapWithKeys(fn (self $case) => [$case->value => $case->getLabel()])
    51	            ->toArray();
    52	    }
    53	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [039]: app/Enums/GenderRestriction.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [040/494]: app/Enums/InstallmentPlanStatus.php
│ LANGUAGE: php | LINES: 62 | SIZE: 1797 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Enums;
     4	
     5	enum InstallmentPlanStatus: string
     6	{
     7	    case Active = 'active';
     8	    case Completed = 'completed';
     9	    case Defaulted = 'defaulted';
    10	    case Cancelled = 'cancelled';
    11	    case PendingApproval = 'pending_approval';
    12	
    13	    public function getLabel(): string
    14	    {
    15	        return match ($this) {
    16	            self::Active => 'نشطة',
    17	            self::Completed => 'مكتملة',
    18	            self::Defaulted => 'متعثرة',
    19	            self::Cancelled => 'ملغاة',
    20	            self::PendingApproval => 'بانتظار الموافقة',
    21	        };
    22	    }
    23	
    24	    public function getLabelEn(): string
    25	    {
    26	        return match ($this) {
    27	            self::Active => 'Active',
    28	            self::Completed => 'Completed',
    29	            self::Defaulted => 'Defaulted',
    30	            self::Cancelled => 'Cancelled',
    31	            self::PendingApproval => 'Pending Approval',
    32	        };
    33	    }
    34	
    35	    public function getColor(): string
    36	    {
    37	        return match ($this) {
    38	            self::Active => 'success',
    39	            self::Completed => 'info',
    40	            self::Defaulted => 'danger',
    41	            self::Cancelled => 'gray',
    42	            self::PendingApproval => 'warning',
    43	        };
    44	    }
    45	
    46	    public function getIcon(): string
    47	    {
    48	        return match ($this) {
    49	            self::Active => 'heroicon-o-play',
    50	            self::Completed => 'heroicon-o-check-circle',
    51	            self::Defaulted => 'heroicon-o-exclamation-circle',
    52	            self::Cancelled => 'heroicon-o-x-circle',
    53	            self::PendingApproval => 'heroicon-o-clock',
    54	        };
    55	    }
    56	
    57	    public static function toFilamentOptions(): array
    58	    {
    59	        return collect(self::cases())
    60	            ->mapWithKeys(fn (self $case) => [$case->value => $case->getLabel()])
    61	            ->toArray();
    62	    }
    63	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [040]: app/Enums/InstallmentPlanStatus.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [041/494]: app/Enums/InstallmentStatus.php
│ LANGUAGE: php | LINES: 67 | SIZE: 1927 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Enums;
     4	
     5	enum InstallmentStatus: string
     6	{
     7	    case Pending = 'pending';
     8	    case Paid = 'paid';
     9	    case Overdue = 'overdue';
    10	    case PartiallyPaid = 'partially_paid';
    11	    case Waived = 'waived';
    12	    case Cancelled = 'cancelled';
    13	
    14	    public function getLabel(): string
    15	    {
    16	        return match ($this) {
    17	            self::Pending => 'معلق',
    18	            self::Paid => 'مدفوع',
    19	            self::Overdue => 'متأخر',
    20	            self::PartiallyPaid => 'مدفوع جزئياً',
    21	            self::Waived => 'معفى',
    22	            self::Cancelled => 'ملغي',
    23	        };
    24	    }
    25	
    26	    public function getLabelEn(): string
    27	    {
    28	        return match ($this) {
    29	            self::Pending => 'Pending',
    30	            self::Paid => 'Paid',
    31	            self::Overdue => 'Overdue',
    32	            self::PartiallyPaid => 'Partially Paid',
    33	            self::Waived => 'Waived',
    34	            self::Cancelled => 'Cancelled',
    35	        };
    36	    }
    37	
    38	    public function getColor(): string
    39	    {
    40	        return match ($this) {
    41	            self::Pending => 'warning',
    42	            self::Paid => 'success',
    43	            self::Overdue => 'danger',
    44	            self::PartiallyPaid => 'info',
    45	            self::Waived => 'gray',
    46	            self::Cancelled => 'gray',
    47	        };
    48	    }
    49	
    50	    public function getIcon(): string
    51	    {
    52	        return match ($this) {
    53	            self::Pending => 'heroicon-o-clock',
    54	            self::Paid => 'heroicon-o-check-circle',
    55	            self::Overdue => 'heroicon-o-exclamation-circle',
    56	            self::PartiallyPaid => 'heroicon-o-minus-circle',
    57	            self::Waived => 'heroicon-o-hand-raised',
    58	            self::Cancelled => 'heroicon-o-x-circle',
    59	        };
    60	    }
    61	
    62	    public static function toFilamentOptions(): array
    63	    {
    64	        return collect(self::cases())
    65	            ->mapWithKeys(fn (self $case) => [$case->value => $case->getLabel()])
    66	            ->toArray();
    67	    }
    68	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [041]: app/Enums/InstallmentStatus.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [042/494]: app/Enums/InterviewResult.php
│ LANGUAGE: php | LINES: 57 | SIZE: 1533 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Enums;
     4	
     5	enum InterviewResult: string
     6	{
     7	    case Passed = 'passed';
     8	    case Failed = 'failed';
     9	    case Pending = 'pending';
    10	    case ConditionalPass = 'conditional_pass';
    11	
    12	    public function getLabel(): string
    13	    {
    14	        return match ($this) {
    15	            self::Passed => 'ناجح',
    16	            self::Failed => 'غير ناجح',
    17	            self::Pending => 'معلق',
    18	            self::ConditionalPass => 'ناجح مشروط',
    19	        };
    20	    }
    21	
    22	    public function getLabelEn(): string
    23	    {
    24	        return match ($this) {
    25	            self::Passed => 'Passed',
    26	            self::Failed => 'Failed',
    27	            self::Pending => 'Pending',
    28	            self::ConditionalPass => 'Conditional Pass',
    29	        };
    30	    }
    31	
    32	    public function getColor(): string
    33	    {
    34	        return match ($this) {
    35	            self::Passed => 'success',
    36	            self::Failed => 'danger',
    37	            self::Pending => 'warning',
    38	            self::ConditionalPass => 'info',
    39	        };
    40	    }
    41	
    42	    public function getIcon(): string
    43	    {
    44	        return match ($this) {
    45	            self::Passed => 'heroicon-o-check-circle',
    46	            self::Failed => 'heroicon-o-x-circle',
    47	            self::Pending => 'heroicon-o-clock',
    48	            self::ConditionalPass => 'heroicon-o-question-mark-circle',
    49	        };
    50	    }
    51	
    52	    public static function toFilamentOptions(): array
    53	    {
    54	        return collect(self::cases())
    55	            ->mapWithKeys(fn (self $case) => [$case->value => $case->getLabel()])
    56	            ->toArray();
    57	    }
    58	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [042]: app/Enums/InterviewResult.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [043/494]: app/Enums/InterviewStatus.php
│ LANGUAGE: php | LINES: 67 | SIZE: 2003 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Enums;
     4	
     5	enum InterviewStatus: string
     6	{
     7	    case Scheduled = 'scheduled';
     8	    case InProgress = 'in_progress';
     9	    case Completed = 'completed';
    10	    case Cancelled = 'cancelled';
    11	    case NoShow = 'no_show';
    12	    case Rescheduled = 'rescheduled';
    13	
    14	    public function getLabel(): string
    15	    {
    16	        return match ($this) {
    17	            self::Scheduled => 'مجدولة',
    18	            self::InProgress => 'جارية',
    19	            self::Completed => 'مكتملة',
    20	            self::Cancelled => 'ملغاة',
    21	            self::NoShow => 'لم يحضر',
    22	            self::Rescheduled => 'أعيد جدولتها',
    23	        };
    24	    }
    25	
    26	    public function getLabelEn(): string
    27	    {
    28	        return match ($this) {
    29	            self::Scheduled => 'Scheduled',
    30	            self::InProgress => 'In Progress',
    31	            self::Completed => 'Completed',
    32	            self::Cancelled => 'Cancelled',
    33	            self::NoShow => 'No Show',
    34	            self::Rescheduled => 'Rescheduled',
    35	        };
    36	    }
    37	
    38	    public function getColor(): string
    39	    {
    40	        return match ($this) {
    41	            self::Scheduled => 'info',
    42	            self::InProgress => 'warning',
    43	            self::Completed => 'success',
    44	            self::Cancelled => 'gray',
    45	            self::NoShow => 'danger',
    46	            self::Rescheduled => 'warning',
    47	        };
    48	    }
    49	
    50	    public function getIcon(): string
    51	    {
    52	        return match ($this) {
    53	            self::Scheduled => 'heroicon-o-calendar',
    54	            self::InProgress => 'heroicon-o-chat-bubble-left-right',
    55	            self::Completed => 'heroicon-o-check-circle',
    56	            self::Cancelled => 'heroicon-o-x-circle',
    57	            self::NoShow => 'heroicon-o-user-minus',
    58	            self::Rescheduled => 'heroicon-o-arrow-path',
    59	        };
    60	    }
    61	
    62	    public static function toFilamentOptions(): array
    63	    {
    64	        return collect(self::cases())
    65	            ->mapWithKeys(fn (self $case) => [$case->value => $case->getLabel()])
    66	            ->toArray();
    67	    }
    68	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [043]: app/Enums/InterviewStatus.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [044/494]: app/Enums/MembershipStatus.php
│ LANGUAGE: php | LINES: 155 | SIZE: 5219 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Enums;
     4	
     5	enum MembershipStatus: string
     6	{
     7	    case Applicant = 'applicant';
     8	    case PendingDocuments = 'pending_documents';
     9	    case PendingInterview = 'pending_interview';
    10	    case PendingBoardReview = 'pending_board_review';
    11	    case PendingPayment = 'pending_payment';
    12	    case Active = 'active';
    13	    case Suspended = 'suspended';
    14	    case Frozen = 'frozen';
    15	    case Expelled = 'expelled';
    16	    case Withdrawn = 'withdrawn';
    17	    case Deceased = 'deceased';
    18	    case Rejected = 'rejected';
    19	    case Lapsed = 'lapsed';
    20	    case TransferredOut = 'transferred_out';
    21	    case Honorary = 'honorary';
    22	
    23	    public function getLabel(): string
    24	    {
    25	        return match ($this) {
    26	            self::Applicant => 'مقدم طلب',
    27	            self::PendingDocuments => 'بانتظار المستندات',
    28	            self::PendingInterview => 'بانتظار المقابلة',
    29	            self::PendingBoardReview => 'بانتظار مراجعة المجلس',
    30	            self::PendingPayment => 'بانتظار السداد',
    31	            self::Active => 'نشط',
    32	            self::Suspended => 'موقوف',
    33	            self::Frozen => 'مجمد',
    34	            self::Expelled => 'مفصول',
    35	            self::Withdrawn => 'منسحب',
    36	            self::Deceased => 'متوفى',
    37	            self::Rejected => 'مرفوض',
    38	            self::Lapsed => 'منتهي',
    39	            self::TransferredOut => 'منقول',
    40	            self::Honorary => 'فخري',
    41	        };
    42	    }
    43	
    44	    public function getLabelEn(): string
    45	    {
    46	        return match ($this) {
    47	            self::Applicant => 'Applicant',
    48	            self::PendingDocuments => 'Pending Documents',
    49	            self::PendingInterview => 'Pending Interview',
    50	            self::PendingBoardReview => 'Pending Board Review',
    51	            self::PendingPayment => 'Pending Payment',
    52	            self::Active => 'Active',
    53	            self::Suspended => 'Suspended',
    54	            self::Frozen => 'Frozen',
    55	            self::Expelled => 'Expelled',
    56	            self::Withdrawn => 'Withdrawn',
    57	            self::Deceased => 'Deceased',
    58	            self::Rejected => 'Rejected',
    59	            self::Lapsed => 'Lapsed',
    60	            self::TransferredOut => 'Transferred Out',
    61	            self::Honorary => 'Honorary',
    62	        };
    63	    }
    64	
    65	    public function getColor(): string
    66	    {
    67	        return match ($this) {
    68	            self::Applicant => 'gray',
    69	            self::PendingDocuments => 'warning',
    70	            self::PendingInterview => 'warning',
    71	            self::PendingBoardReview => 'warning',
    72	            self::PendingPayment => 'warning',
    73	            self::Active => 'success',
    74	            self::Suspended => 'danger',
    75	            self::Frozen => 'info',
    76	            self::Expelled => 'danger',
    77	            self::Withdrawn => 'gray',
    78	            self::Deceased => 'gray',
    79	            self::Rejected => 'danger',
    80	            self::Lapsed => 'warning',
    81	            self::TransferredOut => 'info',
    82	            self::Honorary => 'primary',
    83	        };
    84	    }
    85	
    86	    public function getIcon(): string
    87	    {
    88	        return match ($this) {
    89	            self::Applicant => 'heroicon-o-document-plus',
    90	            self::PendingDocuments => 'heroicon-o-document',
    91	            self::PendingInterview => 'heroicon-o-chat-bubble-left-right',
    92	            self::PendingBoardReview => 'heroicon-o-clipboard-document-check',
    93	            self::PendingPayment => 'heroicon-o-banknotes',
    94	            self::Active => 'heroicon-o-check-circle',
    95	            self::Suspended => 'heroicon-o-pause-circle',
    96	            self::Frozen => 'heroicon-o-stop-circle',
    97	            self::Expelled => 'heroicon-o-x-circle',
    98	            self::Withdrawn => 'heroicon-o-arrow-right-on-rectangle',
    99	            self::Deceased => 'heroicon-o-heart',
   100	            self::Rejected => 'heroicon-o-x-mark',
   101	            self::Lapsed => 'heroicon-o-clock',
   102	            self::TransferredOut => 'heroicon-o-arrow-right',
   103	            self::Honorary => 'heroicon-o-star',
   104	        };
   105	    }
   106	
   107	    public function isActive(): bool
   108	    {
   109	        return $this === self::Active || $this === self::Honorary;
   110	    }
   111	
   112	    public function isPending(): bool
   113	    {
   114	        return in_array($this, [
   115	            self::Applicant,
   116	            self::PendingDocuments,
   117	            self::PendingInterview,
   118	            self::PendingBoardReview,
   119	            self::PendingPayment,
   120	        ]);
   121	    }
   122	
   123	    public function isTerminal(): bool
   124	    {
   125	        return in_array($this, [
   126	            self::Expelled,
   127	            self::Withdrawn,
   128	            self::Deceased,
   129	            self::Rejected,
   130	            self::TransferredOut,
   131	        ]);
   132	    }
   133	
   134	    public static function toFilamentOptions(): array
   135	    {
   136	        return collect(self::cases())
   137	            ->mapWithKeys(fn (self $case) => [$case->value => $case->getLabel()])
   138	            ->toArray();
   139	    }
   140	
   141	    public static function activeStatuses(): array
   142	    {
   143	        return [self::Active, self::Honorary];
   144	    }
   145	
   146	    public static function pendingStatuses(): array
   147	    {
   148	        return [
   149	            self::Applicant,
   150	            self::PendingDocuments,
   151	            self::PendingInterview,
   152	            self::PendingBoardReview,
   153	            self::PendingPayment,
   154	        ];
   155	    }
   156	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [044]: app/Enums/MembershipStatus.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [045/494]: app/Enums/MembershipType.php
│ LANGUAGE: php | LINES: 67 | SIZE: 2040 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Enums;
     4	
     5	enum MembershipType: string
     6	{
     7	    case Working = 'working';
     8	    case Affiliate = 'affiliate';
     9	    case Honorary = 'honorary';
    10	    case Seasonal = 'seasonal';
    11	    case Temporary = 'temporary';
    12	    case SportingAssociate = 'sporting_associate';
    13	
    14	    public function getLabel(): string
    15	    {
    16	        return match ($this) {
    17	            self::Working => 'عضو عامل',
    18	            self::Affiliate => 'عضو منتسب',
    19	            self::Honorary => 'عضو فخري',
    20	            self::Seasonal => 'عضو موسمي',
    21	            self::Temporary => 'عضو مؤقت',
    22	            self::SportingAssociate => 'عضو رياضي',
    23	        };
    24	    }
    25	
    26	    public function getLabelEn(): string
    27	    {
    28	        return match ($this) {
    29	            self::Working => 'Working Member',
    30	            self::Affiliate => 'Affiliate Member',
    31	            self::Honorary => 'Honorary Member',
    32	            self::Seasonal => 'Seasonal Member',
    33	            self::Temporary => 'Temporary Member',
    34	            self::SportingAssociate => 'Sporting Associate',
    35	        };
    36	    }
    37	
    38	    public function getColor(): string
    39	    {
    40	        return match ($this) {
    41	            self::Working => 'primary',
    42	            self::Affiliate => 'info',
    43	            self::Honorary => 'warning',
    44	            self::Seasonal => 'success',
    45	            self::Temporary => 'gray',
    46	            self::SportingAssociate => 'danger',
    47	        };
    48	    }
    49	
    50	    public function getIcon(): string
    51	    {
    52	        return match ($this) {
    53	            self::Working => 'heroicon-o-briefcase',
    54	            self::Affiliate => 'heroicon-o-link',
    55	            self::Honorary => 'heroicon-o-star',
    56	            self::Seasonal => 'heroicon-o-sun',
    57	            self::Temporary => 'heroicon-o-clock',
    58	            self::SportingAssociate => 'heroicon-o-trophy',
    59	        };
    60	    }
    61	
    62	    public static function toFilamentOptions(): array
    63	    {
    64	        return collect(self::cases())
    65	            ->mapWithKeys(fn (self $case) => [$case->value => $case->getLabel()])
    66	            ->toArray();
    67	    }
    68	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [045]: app/Enums/MembershipType.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [046/494]: app/Enums/NotificationChannel.php
│ LANGUAGE: php | LINES: 57 | SIZE: 1490 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Enums;
     4	
     5	enum NotificationChannel: string
     6	{
     7	    case Database = 'database';
     8	    case Email = 'email';
     9	    case Sms = 'sms';
    10	    case Push = 'push';
    11	
    12	    public function getLabel(): string
    13	    {
    14	        return match ($this) {
    15	            self::Database => 'إشعار نظام',
    16	            self::Email => 'بريد إلكتروني',
    17	            self::Sms => 'رسالة نصية',
    18	            self::Push => 'إشعار فوري',
    19	        };
    20	    }
    21	
    22	    public function getLabelEn(): string
    23	    {
    24	        return match ($this) {
    25	            self::Database => 'System Notification',
    26	            self::Email => 'Email',
    27	            self::Sms => 'SMS',
    28	            self::Push => 'Push Notification',
    29	        };
    30	    }
    31	
    32	    public function getColor(): string
    33	    {
    34	        return match ($this) {
    35	            self::Database => 'info',
    36	            self::Email => 'primary',
    37	            self::Sms => 'success',
    38	            self::Push => 'warning',
    39	        };
    40	    }
    41	
    42	    public function getIcon(): string
    43	    {
    44	        return match ($this) {
    45	            self::Database => 'heroicon-o-bell',
    46	            self::Email => 'heroicon-o-envelope',
    47	            self::Sms => 'heroicon-o-device-phone-mobile',
    48	            self::Push => 'heroicon-o-bell-alert',
    49	        };
    50	    }
    51	
    52	    public static function toFilamentOptions(): array
    53	    {
    54	        return collect(self::cases())
    55	            ->mapWithKeys(fn (self $case) => [$case->value => $case->getLabel()])
    56	            ->toArray();
    57	    }
    58	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [046]: app/Enums/NotificationChannel.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [047/494]: app/Enums/NotificationPriority.php
│ LANGUAGE: php | LINES: 57 | SIZE: 1418 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Enums;
     4	
     5	enum NotificationPriority: string
     6	{
     7	    case Low = 'low';
     8	    case Normal = 'normal';
     9	    case High = 'high';
    10	    case Urgent = 'urgent';
    11	
    12	    public function getLabel(): string
    13	    {
    14	        return match ($this) {
    15	            self::Low => 'منخفضة',
    16	            self::Normal => 'عادية',
    17	            self::High => 'عالية',
    18	            self::Urgent => 'عاجلة',
    19	        };
    20	    }
    21	
    22	    public function getLabelEn(): string
    23	    {
    24	        return match ($this) {
    25	            self::Low => 'Low',
    26	            self::Normal => 'Normal',
    27	            self::High => 'High',
    28	            self::Urgent => 'Urgent',
    29	        };
    30	    }
    31	
    32	    public function getColor(): string
    33	    {
    34	        return match ($this) {
    35	            self::Low => 'gray',
    36	            self::Normal => 'info',
    37	            self::High => 'warning',
    38	            self::Urgent => 'danger',
    39	        };
    40	    }
    41	
    42	    public function getIcon(): string
    43	    {
    44	        return match ($this) {
    45	            self::Low => 'heroicon-o-arrow-down',
    46	            self::Normal => 'heroicon-o-minus',
    47	            self::High => 'heroicon-o-arrow-up',
    48	            self::Urgent => 'heroicon-o-exclamation-triangle',
    49	        };
    50	    }
    51	
    52	    public static function toFilamentOptions(): array
    53	    {
    54	        return collect(self::cases())
    55	            ->mapWithKeys(fn (self $case) => [$case->value => $case->getLabel()])
    56	            ->toArray();
    57	    }
    58	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [047]: app/Enums/NotificationPriority.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [048/494]: app/Enums/PaymentMethod.php
│ LANGUAGE: php | LINES: 40 | SIZE: 1080 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Enums;
     4	
     5	enum PaymentMethod: string
     6	{
     7	    case CASH = 'cash';
     8	    case CHECK = 'check';
     9	    case BANK_TRANSFER = 'bank_transfer';
    10	    case CARD = 'card';
    11	
    12	    public function label(): string
    13	    {
    14	        return match ($this) {
    15	            self::CASH          => 'نقدي',
    16	            self::CHECK         => 'شيك',
    17	            self::BANK_TRANSFER => 'تحويل بنكي',
    18	            self::CARD          => 'بطاقة',
    19	        };
    20	    }
    21	
    22	    public function labelEn(): string
    23	    {
    24	        return match ($this) {
    25	            self::CASH          => 'Cash',
    26	            self::CHECK         => 'Check',
    27	            self::BANK_TRANSFER => 'Bank Transfer',
    28	            self::CARD          => 'Card',
    29	        };
    30	    }
    31	
    32	    public function icon(): string
    33	    {
    34	        return match ($this) {
    35	            self::CASH          => 'heroicon-o-banknotes',
    36	            self::CHECK         => 'heroicon-o-document-check',
    37	            self::BANK_TRANSFER => 'heroicon-o-building-library',
    38	            self::CARD          => 'heroicon-o-credit-card',
    39	        };
    40	    }
    41	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [048]: app/Enums/PaymentMethod.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [049/494]: app/Enums/PaymentStatus.php
│ LANGUAGE: php | LINES: 62 | SIZE: 1696 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Enums;
     4	
     5	enum PaymentStatus: string
     6	{
     7	    case Pending = 'pending';
     8	    case Completed = 'completed';
     9	    case Failed = 'failed';
    10	    case Refunded = 'refunded';
    11	    case Cancelled = 'cancelled';
    12	
    13	    public function getLabel(): string
    14	    {
    15	        return match ($this) {
    16	            self::Pending => 'معلق',
    17	            self::Completed => 'مكتمل',
    18	            self::Failed => 'فشل',
    19	            self::Refunded => 'مسترد',
    20	            self::Cancelled => 'ملغي',
    21	        };
    22	    }
    23	
    24	    public function getLabelEn(): string
    25	    {
    26	        return match ($this) {
    27	            self::Pending => 'Pending',
    28	            self::Completed => 'Completed',
    29	            self::Failed => 'Failed',
    30	            self::Refunded => 'Refunded',
    31	            self::Cancelled => 'Cancelled',
    32	        };
    33	    }
    34	
    35	    public function getColor(): string
    36	    {
    37	        return match ($this) {
    38	            self::Pending => 'warning',
    39	            self::Completed => 'success',
    40	            self::Failed => 'danger',
    41	            self::Refunded => 'info',
    42	            self::Cancelled => 'gray',
    43	        };
    44	    }
    45	
    46	    public function getIcon(): string
    47	    {
    48	        return match ($this) {
    49	            self::Pending => 'heroicon-o-clock',
    50	            self::Completed => 'heroicon-o-check-circle',
    51	            self::Failed => 'heroicon-o-x-circle',
    52	            self::Refunded => 'heroicon-o-arrow-uturn-left',
    53	            self::Cancelled => 'heroicon-o-no-symbol',
    54	        };
    55	    }
    56	
    57	    public static function toFilamentOptions(): array
    58	    {
    59	        return collect(self::cases())
    60	            ->mapWithKeys(fn (self $case) => [$case->value => $case->getLabel()])
    61	            ->toArray();
    62	    }
    63	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [049]: app/Enums/PaymentStatus.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [050/494]: app/Enums/PenaltyStatus.php
│ LANGUAGE: php | LINES: 82 | SIZE: 2616 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Enums;
     4	
     5	enum PenaltyStatus: string
     6	{
     7	    case Imposed = 'imposed';
     8	    case Active = 'active';
     9	    case PendingPayment = 'pending_payment';
    10	    case Paid = 'paid';
    11	    case Served = 'served';
    12	    case Appealed = 'appealed';
    13	    case Overturned = 'overturned';
    14	    case Reduced = 'reduced';
    15	    case Cancelled = 'cancelled';
    16	
    17	    public function getLabel(): string
    18	    {
    19	        return match ($this) {
    20	            self::Imposed => 'مفروضة',
    21	            self::Active => 'نشطة',
    22	            self::PendingPayment => 'بانتظار السداد',
    23	            self::Paid => 'مدفوعة',
    24	            self::Served => 'تم التنفيذ',
    25	            self::Appealed => 'تم الاستئناف',
    26	            self::Overturned => 'ملغاة بحكم',
    27	            self::Reduced => 'مخففة',
    28	            self::Cancelled => 'ملغاة',
    29	        };
    30	    }
    31	
    32	    public function getLabelEn(): string
    33	    {
    34	        return match ($this) {
    35	            self::Imposed => 'Imposed',
    36	            self::Active => 'Active',
    37	            self::PendingPayment => 'Pending Payment',
    38	            self::Paid => 'Paid',
    39	            self::Served => 'Served',
    40	            self::Appealed => 'Appealed',
    41	            self::Overturned => 'Overturned',
    42	            self::Reduced => 'Reduced',
    43	            self::Cancelled => 'Cancelled',
    44	        };
    45	    }
    46	
    47	    public function getColor(): string
    48	    {
    49	        return match ($this) {
    50	            self::Imposed => 'danger',
    51	            self::Active => 'danger',
    52	            self::PendingPayment => 'warning',
    53	            self::Paid => 'success',
    54	            self::Served => 'success',
    55	            self::Appealed => 'info',
    56	            self::Overturned => 'gray',
    57	            self::Reduced => 'warning',
    58	            self::Cancelled => 'gray',
    59	        };
    60	    }
    61	
    62	    public function getIcon(): string
    63	    {
    64	        return match ($this) {
    65	            self::Imposed => 'heroicon-o-exclamation-circle',
    66	            self::Active => 'heroicon-o-fire',
    67	            self::PendingPayment => 'heroicon-o-banknotes',
    68	            self::Paid => 'heroicon-o-check-circle',
    69	            self::Served => 'heroicon-o-check-badge',
    70	            self::Appealed => 'heroicon-o-arrow-path',
    71	            self::Overturned => 'heroicon-o-arrow-uturn-left',
    72	            self::Reduced => 'heroicon-o-arrow-down',
    73	            self::Cancelled => 'heroicon-o-x-circle',
    74	        };
    75	    }
    76	
    77	    public static function toFilamentOptions(): array
    78	    {
    79	        return collect(self::cases())
    80	            ->mapWithKeys(fn (self $case) => [$case->value => $case->getLabel()])
    81	            ->toArray();
    82	    }
    83	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [050]: app/Enums/PenaltyStatus.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [051/494]: app/Enums/ReceiptStatus.php
│ LANGUAGE: php | LINES: 77 | SIZE: 2242 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Enums;
     4	
     5	enum ReceiptStatus: string
     6	{
     7	    case Draft = 'draft';
     8	    case Issued = 'issued';
     9	    case Paid = 'paid';
    10	    case PartiallyPaid = 'partially_paid';
    11	    case Cancelled = 'cancelled';
    12	    case Refunded = 'refunded';
    13	    case Void = 'void';
    14	
    15	    public function getLabel(): string
    16	    {
    17	        return match ($this) {
    18	            self::Draft => 'مسودة',
    19	            self::Issued => 'صادر',
    20	            self::Paid => 'مدفوع',
    21	            self::PartiallyPaid => 'مدفوع جزئياً',
    22	            self::Cancelled => 'ملغي',
    23	            self::Refunded => 'مسترد',
    24	            self::Void => 'باطل',
    25	        };
    26	    }
    27	
    28	    public function getLabelEn(): string
    29	    {
    30	        return match ($this) {
    31	            self::Draft => 'Draft',
    32	            self::Issued => 'Issued',
    33	            self::Paid => 'Paid',
    34	            self::PartiallyPaid => 'Partially Paid',
    35	            self::Cancelled => 'Cancelled',
    36	            self::Refunded => 'Refunded',
    37	            self::Void => 'Void',
    38	        };
    39	    }
    40	
    41	    public function getColor(): string
    42	    {
    43	        return match ($this) {
    44	            self::Draft => 'gray',
    45	            self::Issued => 'info',
    46	            self::Paid => 'success',
    47	            self::PartiallyPaid => 'warning',
    48	            self::Cancelled => 'danger',
    49	            self::Refunded => 'warning',
    50	            self::Void => 'gray',
    51	        };
    52	    }
    53	
    54	    public function getIcon(): string
    55	    {
    56	        return match ($this) {
    57	            self::Draft => 'heroicon-o-pencil-square',
    58	            self::Issued => 'heroicon-o-document-text',
    59	            self::Paid => 'heroicon-o-check-circle',
    60	            self::PartiallyPaid => 'heroicon-o-clock',
    61	            self::Cancelled => 'heroicon-o-x-circle',
    62	            self::Refunded => 'heroicon-o-arrow-uturn-left',
    63	            self::Void => 'heroicon-o-no-symbol',
    64	        };
    65	    }
    66	
    67	    public function isFinal(): bool
    68	    {
    69	        return in_array($this, [self::Paid, self::Cancelled, self::Refunded, self::Void]);
    70	    }
    71	
    72	    public static function toFilamentOptions(): array
    73	    {
    74	        return collect(self::cases())
    75	            ->mapWithKeys(fn (self $case) => [$case->value => $case->getLabel()])
    76	            ->toArray();
    77	    }
    78	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [051]: app/Enums/ReceiptStatus.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [052/494]: app/Enums/SubscriptionStatus.php
│ LANGUAGE: php | LINES: 80 | SIZE: 2228 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Enums;
     4	
     5	enum SubscriptionStatus: string
     6	{
     7	    case PENDING = 'pending';
     8	    case PARTIAL = 'partial';
     9	    case PAID = 'paid';
    10	    case OVERDUE = 'overdue';
    11	    case WAIVED = 'waived';
    12	    case CANCELLED = 'cancelled';
    13	
    14	    public function label(): string
    15	    {
    16	        return match ($this) {
    17	            self::PENDING   => 'معلق',
    18	            self::PARTIAL   => 'مدفوع جزئياً',
    19	            self::PAID      => 'مدفوع',
    20	            self::OVERDUE   => 'متأخر',
    21	            self::WAIVED    => 'معفى',
    22	            self::CANCELLED => 'ملغى',
    23	        };
    24	    }
    25	
    26	    public function labelEn(): string
    27	    {
    28	        return match ($this) {
    29	            self::PENDING   => 'Pending',
    30	            self::PARTIAL   => 'Partial',
    31	            self::PAID      => 'Paid',
    32	            self::OVERDUE   => 'Overdue',
    33	            self::WAIVED    => 'Waived',
    34	            self::CANCELLED => 'Cancelled',
    35	        };
    36	    }
    37	
    38	    public function color(): string
    39	    {
    40	        return match ($this) {
    41	            self::PENDING   => 'warning',
    42	            self::PARTIAL   => 'info',
    43	            self::PAID      => 'success',
    44	            self::OVERDUE   => 'danger',
    45	            self::WAIVED    => 'gray',
    46	            self::CANCELLED => 'gray',
    47	        };
    48	    }
    49	
    50	    public function icon(): string
    51	    {
    52	        return match ($this) {
    53	            self::PENDING   => 'heroicon-o-clock',
    54	            self::PARTIAL   => 'heroicon-o-banknotes',
    55	            self::PAID      => 'heroicon-o-check-circle',
    56	            self::OVERDUE   => 'heroicon-o-exclamation-triangle',
    57	            self::WAIVED    => 'heroicon-o-shield-check',
    58	            self::CANCELLED => 'heroicon-o-x-circle',
    59	        };
    60	    }
    61	
    62	    public function isPayable(): bool
    63	    {
    64	        return in_array($this, [self::PENDING, self::PARTIAL, self::OVERDUE]);
    65	    }
    66	
    67	    public function isTerminal(): bool
    68	    {
    69	        return in_array($this, [self::PAID, self::WAIVED, self::CANCELLED]);
    70	    }
    71	
    72	    public static function payableStatuses(): array
    73	    {
    74	        return [self::PENDING, self::PARTIAL, self::OVERDUE];
    75	    }
    76	
    77	    public static function unpaidStatuses(): array
    78	    {
    79	        return [self::PENDING, self::PARTIAL, self::OVERDUE];
    80	    }
    81	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [052]: app/Enums/SubscriptionStatus.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [053/494]: app/Enums/SuspensionStatus.php
│ LANGUAGE: php | LINES: 62 | SIZE: 1772 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Enums;
     4	
     5	enum SuspensionStatus: string
     6	{
     7	    case Active = 'active';
     8	    case Lifted = 'lifted';
     9	    case Expired = 'expired';
    10	    case Extended = 'extended';
    11	    case ConvertedToExpulsion = 'converted_to_expulsion';
    12	
    13	    public function getLabel(): string
    14	    {
    15	        return match ($this) {
    16	            self::Active => 'ساري',
    17	            self::Lifted => 'تم رفعه',
    18	            self::Expired => 'منتهي',
    19	            self::Extended => 'ممدد',
    20	            self::ConvertedToExpulsion => 'تحول لفصل',
    21	        };
    22	    }
    23	
    24	    public function getLabelEn(): string
    25	    {
    26	        return match ($this) {
    27	            self::Active => 'Active',
    28	            self::Lifted => 'Lifted',
    29	            self::Expired => 'Expired',
    30	            self::Extended => 'Extended',
    31	            self::ConvertedToExpulsion => 'Converted to Expulsion',
    32	        };
    33	    }
    34	
    35	    public function getColor(): string
    36	    {
    37	        return match ($this) {
    38	            self::Active => 'danger',
    39	            self::Lifted => 'success',
    40	            self::Expired => 'gray',
    41	            self::Extended => 'warning',
    42	            self::ConvertedToExpulsion => 'danger',
    43	        };
    44	    }
    45	
    46	    public function getIcon(): string
    47	    {
    48	        return match ($this) {
    49	            self::Active => 'heroicon-o-pause-circle',
    50	            self::Lifted => 'heroicon-o-check-circle',
    51	            self::Expired => 'heroicon-o-clock',
    52	            self::Extended => 'heroicon-o-arrow-path',
    53	            self::ConvertedToExpulsion => 'heroicon-o-x-circle',
    54	        };
    55	    }
    56	
    57	    public static function toFilamentOptions(): array
    58	    {
    59	        return collect(self::cases())
    60	            ->mapWithKeys(fn (self $case) => [$case->value => $case->getLabel()])
    61	            ->toArray();
    62	    }
    63	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [053]: app/Enums/SuspensionStatus.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [054/494]: app/Enums/TransferStatus.php
│ LANGUAGE: php | LINES: 72 | SIZE: 2293 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Enums;
     4	
     5	enum TransferStatus: string
     6	{
     7	    case Requested = 'requested';
     8	    case PendingApproval = 'pending_approval';
     9	    case PendingPayment = 'pending_payment';
    10	    case Approved = 'approved';
    11	    case Completed = 'completed';
    12	    case Rejected = 'rejected';
    13	    case Cancelled = 'cancelled';
    14	
    15	    public function getLabel(): string
    16	    {
    17	        return match ($this) {
    18	            self::Requested => 'مطلوب',
    19	            self::PendingApproval => 'بانتظار الموافقة',
    20	            self::PendingPayment => 'بانتظار السداد',
    21	            self::Approved => 'موافق عليه',
    22	            self::Completed => 'مكتمل',
    23	            self::Rejected => 'مرفوض',
    24	            self::Cancelled => 'ملغي',
    25	        };
    26	    }
    27	
    28	    public function getLabelEn(): string
    29	    {
    30	        return match ($this) {
    31	            self::Requested => 'Requested',
    32	            self::PendingApproval => 'Pending Approval',
    33	            self::PendingPayment => 'Pending Payment',
    34	            self::Approved => 'Approved',
    35	            self::Completed => 'Completed',
    36	            self::Rejected => 'Rejected',
    37	            self::Cancelled => 'Cancelled',
    38	        };
    39	    }
    40	
    41	    public function getColor(): string
    42	    {
    43	        return match ($this) {
    44	            self::Requested => 'info',
    45	            self::PendingApproval => 'warning',
    46	            self::PendingPayment => 'warning',
    47	            self::Approved => 'success',
    48	            self::Completed => 'success',
    49	            self::Rejected => 'danger',
    50	            self::Cancelled => 'gray',
    51	        };
    52	    }
    53	
    54	    public function getIcon(): string
    55	    {
    56	        return match ($this) {
    57	            self::Requested => 'heroicon-o-paper-airplane',
    58	            self::PendingApproval => 'heroicon-o-clock',
    59	            self::PendingPayment => 'heroicon-o-banknotes',
    60	            self::Approved => 'heroicon-o-check',
    61	            self::Completed => 'heroicon-o-check-circle',
    62	            self::Rejected => 'heroicon-o-x-circle',
    63	            self::Cancelled => 'heroicon-o-no-symbol',
    64	        };
    65	    }
    66	
    67	    public static function toFilamentOptions(): array
    68	    {
    69	        return collect(self::cases())
    70	            ->mapWithKeys(fn (self $case) => [$case->value => $case->getLabel()])
    71	            ->toArray();
    72	    }
    73	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [054]: app/Enums/TransferStatus.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [055/494]: app/Enums/TransferType.php
│ LANGUAGE: php | LINES: 57 | SIZE: 1621 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Enums;
     4	
     5	enum TransferType: string
     6	{
     7	    case Upgrade = 'upgrade';
     8	    case Downgrade = 'downgrade';
     9	    case CategoryChange = 'category_change';
    10	    case BranchTransfer = 'branch_transfer';
    11	
    12	    public function getLabel(): string
    13	    {
    14	        return match ($this) {
    15	            self::Upgrade => 'ترقية',
    16	            self::Downgrade => 'تنزيل',
    17	            self::CategoryChange => 'تغيير فئة',
    18	            self::BranchTransfer => 'نقل فرع',
    19	        };
    20	    }
    21	
    22	    public function getLabelEn(): string
    23	    {
    24	        return match ($this) {
    25	            self::Upgrade => 'Upgrade',
    26	            self::Downgrade => 'Downgrade',
    27	            self::CategoryChange => 'Category Change',
    28	            self::BranchTransfer => 'Branch Transfer',
    29	        };
    30	    }
    31	
    32	    public function getColor(): string
    33	    {
    34	        return match ($this) {
    35	            self::Upgrade => 'success',
    36	            self::Downgrade => 'danger',
    37	            self::CategoryChange => 'info',
    38	            self::BranchTransfer => 'warning',
    39	        };
    40	    }
    41	
    42	    public function getIcon(): string
    43	    {
    44	        return match ($this) {
    45	            self::Upgrade => 'heroicon-o-arrow-up-circle',
    46	            self::Downgrade => 'heroicon-o-arrow-down-circle',
    47	            self::CategoryChange => 'heroicon-o-arrows-right-left',
    48	            self::BranchTransfer => 'heroicon-o-building-office',
    49	        };
    50	    }
    51	
    52	    public static function toFilamentOptions(): array
    53	    {
    54	        return collect(self::cases())
    55	            ->mapWithKeys(fn (self $case) => [$case->value => $case->getLabel()])
    56	            ->toArray();
    57	    }
    58	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [055]: app/Enums/TransferType.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [056/494]: app/Enums/ViolationSeverity.php
│ LANGUAGE: php | LINES: 57 | SIZE: 1481 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Enums;
     4	
     5	enum ViolationSeverity: string
     6	{
     7	    case Minor = 'minor';
     8	    case Moderate = 'moderate';
     9	    case Major = 'major';
    10	    case Critical = 'critical';
    11	
    12	    public function getLabel(): string
    13	    {
    14	        return match ($this) {
    15	            self::Minor => 'بسيطة',
    16	            self::Moderate => 'متوسطة',
    17	            self::Major => 'جسيمة',
    18	            self::Critical => 'حرجة',
    19	        };
    20	    }
    21	
    22	    public function getLabelEn(): string
    23	    {
    24	        return match ($this) {
    25	            self::Minor => 'Minor',
    26	            self::Moderate => 'Moderate',
    27	            self::Major => 'Major',
    28	            self::Critical => 'Critical',
    29	        };
    30	    }
    31	
    32	    public function getColor(): string
    33	    {
    34	        return match ($this) {
    35	            self::Minor => 'info',
    36	            self::Moderate => 'warning',
    37	            self::Major => 'danger',
    38	            self::Critical => 'danger',
    39	        };
    40	    }
    41	
    42	    public function getIcon(): string
    43	    {
    44	        return match ($this) {
    45	            self::Minor => 'heroicon-o-information-circle',
    46	            self::Moderate => 'heroicon-o-exclamation-triangle',
    47	            self::Major => 'heroicon-o-exclamation-circle',
    48	            self::Critical => 'heroicon-o-fire',
    49	        };
    50	    }
    51	
    52	    public static function toFilamentOptions(): array
    53	    {
    54	        return collect(self::cases())
    55	            ->mapWithKeys(fn (self $case) => [$case->value => $case->getLabel()])
    56	            ->toArray();
    57	    }
    58	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [056]: app/Enums/ViolationSeverity.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [057/494]: app/Enums/ViolationStatus.php
│ LANGUAGE: php | LINES: 77 | SIZE: 2659 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Enums;
     4	
     5	enum ViolationStatus: string
     6	{
     7	    case Reported = 'reported';
     8	    case UnderInvestigation = 'under_investigation';
     9	    case PendingHearing = 'pending_hearing';
    10	    case PendingBoardDecision = 'pending_board_decision';
    11	    case Resolved = 'resolved';
    12	    case Dismissed = 'dismissed';
    13	    case Appealed = 'appealed';
    14	    case Closed = 'closed';
    15	
    16	    public function getLabel(): string
    17	    {
    18	        return match ($this) {
    19	            self::Reported => 'تم الإبلاغ',
    20	            self::UnderInvestigation => 'قيد التحقيق',
    21	            self::PendingHearing => 'بانتظار الجلسة',
    22	            self::PendingBoardDecision => 'بانتظار قرار المجلس',
    23	            self::Resolved => 'تم الحل',
    24	            self::Dismissed => 'مرفوض',
    25	            self::Appealed => 'تم الاستئناف',
    26	            self::Closed => 'مغلق',
    27	        };
    28	    }
    29	
    30	    public function getLabelEn(): string
    31	    {
    32	        return match ($this) {
    33	            self::Reported => 'Reported',
    34	            self::UnderInvestigation => 'Under Investigation',
    35	            self::PendingHearing => 'Pending Hearing',
    36	            self::PendingBoardDecision => 'Pending Board Decision',
    37	            self::Resolved => 'Resolved',
    38	            self::Dismissed => 'Dismissed',
    39	            self::Appealed => 'Appealed',
    40	            self::Closed => 'Closed',
    41	        };
    42	    }
    43	
    44	    public function getColor(): string
    45	    {
    46	        return match ($this) {
    47	            self::Reported => 'warning',
    48	            self::UnderInvestigation => 'info',
    49	            self::PendingHearing => 'warning',
    50	            self::PendingBoardDecision => 'warning',
    51	            self::Resolved => 'success',
    52	            self::Dismissed => 'gray',
    53	            self::Appealed => 'danger',
    54	            self::Closed => 'gray',
    55	        };
    56	    }
    57	
    58	    public function getIcon(): string
    59	    {
    60	        return match ($this) {
    61	            self::Reported => 'heroicon-o-flag',
    62	            self::UnderInvestigation => 'heroicon-o-magnifying-glass',
    63	            self::PendingHearing => 'heroicon-o-chat-bubble-bottom-center-text',
    64	            self::PendingBoardDecision => 'heroicon-o-clipboard-document-check',
    65	            self::Resolved => 'heroicon-o-check-circle',
    66	            self::Dismissed => 'heroicon-o-x-mark',
    67	            self::Appealed => 'heroicon-o-arrow-path',
    68	            self::Closed => 'heroicon-o-lock-closed',
    69	        };
    70	    }
    71	
    72	    public static function toFilamentOptions(): array
    73	    {
    74	        return collect(self::cases())
    75	            ->mapWithKeys(fn (self $case) => [$case->value => $case->getLabel()])
    76	            ->toArray();
    77	    }
    78	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [057]: app/Enums/ViolationStatus.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [058/494]: app/Enums/WorkflowProgressStatus.php
│ LANGUAGE: php | LINES: 67 | SIZE: 1922 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Enums;
     4	
     5	enum WorkflowProgressStatus: string
     6	{
     7	    case Pending = 'pending';
     8	    case InProgress = 'in_progress';
     9	    case Completed = 'completed';
    10	    case Skipped = 'skipped';
    11	    case Failed = 'failed';
    12	    case Blocked = 'blocked';
    13	
    14	    public function getLabel(): string
    15	    {
    16	        return match ($this) {
    17	            self::Pending => 'معلق',
    18	            self::InProgress => 'قيد التنفيذ',
    19	            self::Completed => 'مكتمل',
    20	            self::Skipped => 'تم تخطيه',
    21	            self::Failed => 'فشل',
    22	            self::Blocked => 'محظور',
    23	        };
    24	    }
    25	
    26	    public function getLabelEn(): string
    27	    {
    28	        return match ($this) {
    29	            self::Pending => 'Pending',
    30	            self::InProgress => 'In Progress',
    31	            self::Completed => 'Completed',
    32	            self::Skipped => 'Skipped',
    33	            self::Failed => 'Failed',
    34	            self::Blocked => 'Blocked',
    35	        };
    36	    }
    37	
    38	    public function getColor(): string
    39	    {
    40	        return match ($this) {
    41	            self::Pending => 'gray',
    42	            self::InProgress => 'warning',
    43	            self::Completed => 'success',
    44	            self::Skipped => 'info',
    45	            self::Failed => 'danger',
    46	            self::Blocked => 'danger',
    47	        };
    48	    }
    49	
    50	    public function getIcon(): string
    51	    {
    52	        return match ($this) {
    53	            self::Pending => 'heroicon-o-clock',
    54	            self::InProgress => 'heroicon-o-arrow-path',
    55	            self::Completed => 'heroicon-o-check-circle',
    56	            self::Skipped => 'heroicon-o-forward',
    57	            self::Failed => 'heroicon-o-x-circle',
    58	            self::Blocked => 'heroicon-o-no-symbol',
    59	        };
    60	    }
    61	
    62	    public static function toFilamentOptions(): array
    63	    {
    64	        return collect(self::cases())
    65	            ->mapWithKeys(fn (self $case) => [$case->value => $case->getLabel()])
    66	            ->toArray();
    67	    }
    68	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [058]: app/Enums/WorkflowProgressStatus.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [059/494]: app/Exceptions/BusinessRuleException.php
│ LANGUAGE: php | LINES: 20 | SIZE: 438 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Exceptions;
     4	
     5	use Exception;
     6	
     7	class BusinessRuleException extends Exception
     8	{
     9	    protected string $ruleKey;
    10	
    11	    public function __construct(string $message, string $ruleKey = '', int $code = 422, ?\Throwable $previous = null)
    12	    {
    13	        $this->ruleKey = $ruleKey;
    14	        parent::__construct($message, $code, $previous);
    15	    }
    16	
    17	    public function getRuleKey(): string
    18	    {
    19	        return $this->ruleKey;
    20	    }
    21	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [059]: app/Exceptions/BusinessRuleException.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [060/494]: app/Exceptions/DependentAgeRestrictionException.php
│ LANGUAGE: php | LINES: 10 | SIZE: 156 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	declare(strict_types=1);
     4	
     5	namespace App\Exceptions;
     6	
     7	use RuntimeException;
     8	
     9	final class DependentAgeRestrictionException extends RuntimeException
    10	{
    11	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [060]: app/Exceptions/DependentAgeRestrictionException.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [061/494]: app/Exceptions/DependentLimitExceededException.php
│ LANGUAGE: php | LINES: 10 | SIZE: 155 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	declare(strict_types=1);
     4	
     5	namespace App\Exceptions;
     6	
     7	use RuntimeException;
     8	
     9	final class DependentLimitExceededException extends RuntimeException
    10	{
    11	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [061]: app/Exceptions/DependentLimitExceededException.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [062/494]: app/Exceptions/DuplicateNationalIdException.php
│ LANGUAGE: php | LINES: 10 | SIZE: 152 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	declare(strict_types=1);
     4	
     5	namespace App\Exceptions;
     6	
     7	use RuntimeException;
     8	
     9	final class DuplicateNationalIdException extends RuntimeException
    10	{
    11	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [062]: app/Exceptions/DuplicateNationalIdException.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [063/494]: app/Exceptions/InsufficientBalanceException.php
│ LANGUAGE: php | LINES: 10 | SIZE: 301 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Exceptions;
     4	
     5	class InsufficientBalanceException extends BusinessRuleException
     6	{
     7	    public function __construct(string $message = 'رصيد غير كافي', ?\Throwable $previous = null)
     8	    {
     9	        parent::__construct($message, 'insufficient_balance', 422, $previous);
    10	    }
    11	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [063]: app/Exceptions/InsufficientBalanceException.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [064/494]: app/Exceptions/InvalidStatusTransitionException.php
│ LANGUAGE: php | LINES: 11 | SIZE: 157 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	declare(strict_types=1);
     4	
     5	namespace App\Exceptions;
     6	
     7	use RuntimeException;
     8	
     9	class InvalidStatusTransitionException extends RuntimeException
    10	{
    11	    //
    12	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [064]: app/Exceptions/InvalidStatusTransitionException.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [065/494]: app/Exceptions/MemberNotEligibleException.php
│ LANGUAGE: php | LINES: 17 | SIZE: 491 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Exceptions;
     4	
     5	class MemberNotEligibleException extends BusinessRuleException
     6	{
     7	    public function __construct(string $action = '', string $reason = '', ?\Throwable $previous = null)
     8	    {
     9	        $message = 'العضو غير مؤهل';
    10	        if ($action) {
    11	            $message .= " لـ {$action}";
    12	        }
    13	        if ($reason) {
    14	            $message .= ": {$reason}";
    15	        }
    16	        parent::__construct($message, 'member_not_eligible', 422, $previous);
    17	    }
    18	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [065]: app/Exceptions/MemberNotEligibleException.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [066/494]: app/Exceptions/SequenceGenerationException.php
│ LANGUAGE: php | LINES: 15 | SIZE: 406 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Exceptions;
     4	
     5	class SequenceGenerationException extends BusinessRuleException
     6	{
     7	    public function __construct(string $sequenceKey, ?\Throwable $previous = null)
     8	    {
     9	        parent::__construct(
    10	            "فشل في توليد رقم تسلسلي للمفتاح: {$sequenceKey}",
    11	            'sequence_generation_failed',
    12	            500,
    13	            $previous
    14	        );
    15	    }
    16	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [066]: app/Exceptions/SequenceGenerationException.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [067/494]: app/Exports/FinancialExport.php
│ LANGUAGE: php | LINES: 81 | SIZE: 2576 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Exports;
     4	
     5	use App\Services\Reporting\FinancialReportService;
     6	use Maatwebsite\Excel\Concerns\FromQuery;
     7	use Maatwebsite\Excel\Concerns\WithHeadings;
     8	use Maatwebsite\Excel\Concerns\WithMapping;
     9	use Maatwebsite\Excel\Concerns\WithStyles;
    10	use Maatwebsite\Excel\Concerns\ShouldAutoSize;
    11	use Maatwebsite\Excel\Concerns\WithTitle;
    12	use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
    13	use PhpOffice\PhpSpreadsheet\Style\Alignment;
    14	
    15	class FinancialExport implements FromQuery, WithHeadings, WithMapping, WithStyles, ShouldAutoSize, WithTitle
    16	{
    17	    public function __construct(
    18	        protected array $filters = []
    19	    ) {}
    20	
    21	    public function query()
    22	    {
    23	        $service = app(FinancialReportService::class);
    24	
    25	        return $service->getReceiptsQuery($this->filters);
    26	    }
    27	
    28	    public function headings(): array
    29	    {
    30	        return [
    31	            'رقم الإيصال',
    32	            'رقم العضوية',
    33	            'اسم العضو',
    34	            'تاريخ الإيصال',
    35	            'المبلغ الإجمالي',
    36	            'الخصم',
    37	            'الضريبة',
    38	            'صافي المبلغ',
    39	            'طريقة الدفع',
    40	            'حالة الإيصال',
    41	            'بواسطة',
    42	            'ملاحظات',
    43	        ];
    44	    }
    45	
    46	    public function map($receipt): array
    47	    {
    48	        return [
    49	            $receipt->receipt_number,
    50	            $receipt->member?->membership_number ?? '',
    51	            $receipt->member?->full_name_ar ?? '',
    52	            $receipt->receipt_date?->format('d/m/Y') ?? '',
    53	            number_format($receipt->total_amount, 2),
    54	            number_format($receipt->discount_amount ?? 0, 2),
    55	            number_format($receipt->tax_amount ?? 0, 2),
    56	            number_format($receipt->net_amount ?? $receipt->total_amount, 2),
    57	            $receipt->payment_method ?? '',
    58	            $receipt->status?->getLabel() ?? '',
    59	            $receipt->createdByUser?->name ?? '',
    60	            $receipt->notes ?? '',
    61	        ];
    62	    }
    63	
    64	    public function styles(Worksheet $sheet): array
    65	    {
    66	        return [
    67	            1 => [
    68	                'font' => ['bold' => true, 'color' => ['argb' => 'FFFFFFFF']],
    69	                'fill' => [
    70	                    'fillType' => \PhpOffice\PhpSpreadsheet\Style\Fill::FILL_SOLID,
    71	                    'startColor' => ['argb' => 'FF2E7D32'],
    72	                ],
    73	                'alignment' => ['horizontal' => Alignment::HORIZONTAL_CENTER],
    74	            ],
    75	        ];
    76	    }
    77	
    78	    public function title(): string
    79	    {
    80	        return 'التقرير المالي';
    81	    }
    82	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [067]: app/Exports/FinancialExport.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [068/494]: app/Exports/MembersExport.php
│ LANGUAGE: php | LINES: 82 | SIZE: 2597 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Exports;
     4	
     5	use App\Services\Reporting\MembershipReportService;
     6	use Maatwebsite\Excel\Concerns\FromQuery;
     7	use Maatwebsite\Excel\Concerns\WithHeadings;
     8	use Maatwebsite\Excel\Concerns\WithMapping;
     9	use Maatwebsite\Excel\Concerns\WithStyles;
    10	use Maatwebsite\Excel\Concerns\ShouldAutoSize;
    11	use Maatwebsite\Excel\Concerns\WithTitle;
    12	use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
    13	use PhpOffice\PhpSpreadsheet\Style\Alignment;
    14	
    15	class MembersExport implements FromQuery, WithHeadings, WithMapping, WithStyles, ShouldAutoSize, WithTitle
    16	{
    17	    public function __construct(
    18	        protected array $filters = []
    19	    ) {}
    20	
    21	    public function query()
    22	    {
    23	        $service = app(MembershipReportService::class);
    24	
    25	        return $service->getMembersQuery($this->filters);
    26	    }
    27	
    28	    public function headings(): array
    29	    {
    30	        return [
    31	            'رقم العضوية',
    32	            'الاسم بالعربية',
    33	            'الاسم بالإنجليزية',
    34	            'الرقم القومي',
    35	            'نوع العضوية',
    36	            'حالة العضوية',
    37	            'الجنس',
    38	            'تاريخ الميلاد',
    39	            'الهاتف',
    40	            'البريد الإلكتروني',
    41	            'تاريخ الانضمام',
    42	            'عدد التابعين',
    43	        ];
    44	    }
    45	
    46	    public function map($member): array
    47	    {
    48	        return [
    49	            $member->membership_number,
    50	            $member->full_name_ar,
    51	            $member->full_name_en,
    52	            $member->national_id,
    53	            $member->membershipType?->name_ar ?? '',
    54	            $member->membership_status?->getLabel() ?? '',
    55	            $member->gender?->getLabel() ?? '',
    56	            $member->date_of_birth?->format('d/m/Y') ?? '',
    57	            $member->phone,
    58	            $member->email,
    59	            $member->join_date?->format('d/m/Y') ?? '',
    60	            $member->dependents_count ?? $member->dependents()->count(),
    61	        ];
    62	    }
    63	
    64	    public function styles(Worksheet $sheet): array
    65	    {
    66	        return [
    67	            1 => [
    68	                'font' => ['bold' => true, 'size' => 12],
    69	                'fill' => [
    70	                    'fillType' => \PhpOffice\PhpSpreadsheet\Style\Fill::FILL_SOLID,
    71	                    'startColor' => ['argb' => 'FF4472C4'],
    72	                ],
    73	                'font' => ['bold' => true, 'color' => ['argb' => 'FFFFFFFF']],
    74	                'alignment' => ['horizontal' => Alignment::HORIZONTAL_CENTER],
    75	            ],
    76	        ];
    77	    }
    78	
    79	    public function title(): string
    80	    {
    81	        return 'تقرير العضويات';
    82	    }
    83	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [068]: app/Exports/MembersExport.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [069/494]: app/Exports/SubscriptionsExport.php
│ LANGUAGE: php | LINES: 71 | SIZE: 2207 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Exports;
     4	
     5	use App\Services\Reporting\SubscriptionReportService;
     6	use Maatwebsite\Excel\Concerns\FromQuery;
     7	use Maatwebsite\Excel\Concerns\WithHeadings;
     8	use Maatwebsite\Excel\Concerns\WithMapping;
     9	use Maatwebsite\Excel\Concerns\WithStyles;
    10	use Maatwebsite\Excel\Concerns\ShouldAutoSize;
    11	use Maatwebsite\Excel\Concerns\WithTitle;
    12	use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
    13	use PhpOffice\PhpSpreadsheet\Style\Alignment;
    14	
    15	class SubscriptionsExport implements FromQuery, WithHeadings, WithMapping, WithStyles, ShouldAutoSize, WithTitle
    16	{
    17	    public function __construct(
    18	        protected array $filters = []
    19	    ) {}
    20	
    21	    public function query()
    22	    {
    23	        $service = app(SubscriptionReportService::class);
    24	
    25	        return $service->getSubscriptionsQuery($this->filters);
    26	    }
    27	
    28	    public function headings(): array
    29	    {
    30	        return [
    31	            'رقم العضوية',
    32	            'اسم العضو',
    33	            'خطة الاشتراك',
    34	            'تاريخ البداية',
    35	            'تاريخ النهاية',
    36	            'الحالة',
    37	            'المبلغ',
    38	        ];
    39	    }
    40	
    41	    public function map($subscription): array
    42	    {
    43	        return [
    44	            $subscription->member?->membership_number ?? '',
    45	            $subscription->member?->full_name_ar ?? '',
    46	            $subscription->subscriptionPlan?->name_ar ?? '',
    47	            $subscription->start_date?->format('d/m/Y') ?? '',
    48	            $subscription->end_date?->format('d/m/Y') ?? '',
    49	            $subscription->status?->getLabel() ?? '',
    50	            number_format($subscription->amount ?? 0, 2),
    51	        ];
    52	    }
    53	
    54	    public function styles(Worksheet $sheet): array
    55	    {
    56	        return [
    57	            1 => [
    58	                'font' => ['bold' => true, 'color' => ['argb' => 'FFFFFFFF']],
    59	                'fill' => [
    60	                    'fillType' => \PhpOffice\PhpSpreadsheet\Style\Fill::FILL_SOLID,
    61	                    'startColor' => ['argb' => 'FF1565C0'],
    62	                ],
    63	                'alignment' => ['horizontal' => Alignment::HORIZONTAL_CENTER],
    64	            ],
    65	        ];
    66	    }
    67	
    68	    public function title(): string
    69	    {
    70	        return 'تقرير الاشتراكات';
    71	    }
    72	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [069]: app/Exports/SubscriptionsExport.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [070/494]: app/Exports/ViolationsExport.php
│ LANGUAGE: php | LINES: 75 | SIZE: 2270 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Exports;
     4	
     5	use App\Services\Reporting\ViolationReportService;
     6	use Maatwebsite\Excel\Concerns\FromQuery;
     7	use Maatwebsite\Excel\Concerns\WithHeadings;
     8	use Maatwebsite\Excel\Concerns\WithMapping;
     9	use Maatwebsite\Excel\Concerns\WithStyles;
    10	use Maatwebsite\Excel\Concerns\ShouldAutoSize;
    11	use Maatwebsite\Excel\Concerns\WithTitle;
    12	use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
    13	use PhpOffice\PhpSpreadsheet\Style\Alignment;
    14	
    15	class ViolationsExport implements FromQuery, WithHeadings, WithMapping, WithStyles, ShouldAutoSize, WithTitle
    16	{
    17	    public function __construct(
    18	        protected array $filters = []
    19	    ) {}
    20	
    21	    public function query()
    22	    {
    23	        $service = app(ViolationReportService::class);
    24	
    25	        return $service->getViolationsQuery($this->filters);
    26	    }
    27	
    28	    public function headings(): array
    29	    {
    30	        return [
    31	            '#',
    32	            'رقم العضوية',
    33	            'اسم العضو',
    34	            'نوع المخالفة',
    35	            'الخطورة',
    36	            'الحالة',
    37	            'تاريخ المخالفة',
    38	            'المبلّغ',
    39	            'الوصف',
    40	        ];
    41	    }
    42	
    43	    public function map($violation): array
    44	    {
    45	        return [
    46	            $violation->id,
    47	            $violation->member?->membership_number ?? '',
    48	            $violation->member?->full_name_ar ?? '',
    49	            $violation->violationType?->name_ar ?? '',
    50	            $violation->severity?->getLabel() ?? '',
    51	            $violation->status?->getLabel() ?? '',
    52	            $violation->violation_date?->format('d/m/Y') ?? '',
    53	            $violation->reportedByUser?->name ?? '',
    54	            $violation->description ?? '',
    55	        ];
    56	    }
    57	
    58	    public function styles(Worksheet $sheet): array
    59	    {
    60	        return [
    61	            1 => [
    62	                'font' => ['bold' => true, 'color' => ['argb' => 'FFFFFFFF']],
    63	                'fill' => [
    64	                    'fillType' => \PhpOffice\PhpSpreadsheet\Style\Fill::FILL_SOLID,
    65	                    'startColor' => ['argb' => 'FFC62828'],
    66	                ],
    67	                'alignment' => ['horizontal' => Alignment::HORIZONTAL_CENTER],
    68	            ],
    69	        ];
    70	    }
    71	
    72	    public function title(): string
    73	    {
    74	        return 'تقرير المخالفات';
    75	    }
    76	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [070]: app/Exports/ViolationsExport.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [071/494]: app/Filament/Pages/BackupManagement.php
│ LANGUAGE: php | LINES: 122 | SIZE: 4506 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Filament\Pages;
     4	
     5	use App\Services\Admin\BackupService;
     6	use Filament\Pages\Page;
     7	use Filament\Actions\Action;
     8	use Filament\Notifications\Notification;
     9	use Filament\Tables;
    10	use Filament\Tables\Concerns\InteractsWithTable;
    11	use Filament\Tables\Contracts\HasTable;
    12	use Filament\Tables\Table;
    13	use Illuminate\Database\Eloquent\Builder;
    14	use Illuminate\Support\Collection;
    15	
    16	class BackupManagement extends Page implements HasTable
    17	{
    18	    use InteractsWithTable;
    19	
    20	    protected static ?string $navigationIcon = 'heroicon-o-server-stack';
    21	
    22	    protected static ?string $navigationGroup = 'إدارة النظام';
    23	
    24	    protected static ?string $navigationLabel = 'النسخ الاحتياطية';
    25	
    26	    protected static ?string $title = 'إدارة النسخ الاحتياطية';
    27	
    28	    protected static ?int $navigationSort = 7;
    29	
    30	    protected static string $view = 'filament.pages.backup-management';
    31	
    32	    public Collection $backups;
    33	
    34	    public function mount(): void
    35	    {
    36	        $this->refreshBackups();
    37	    }
    38	
    39	    public function refreshBackups(): void
    40	    {
    41	        $this->backups = app(BackupService::class)->listBackups();
    42	    }
    43	
    44	    protected function getHeaderActions(): array
    45	    {
    46	        return [
    47	            Action::make('createBackup')
    48	                ->label('إنشاء نسخة احتياطية')
    49	                ->icon('heroicon-o-plus-circle')
    50	                ->color('primary')
    51	                ->requiresConfirmation()
    52	                ->modalHeading('إنشاء نسخة احتياطية جديدة')
    53	                ->modalDescription('سيتم إنشاء نسخة احتياطية كاملة لقاعدة البيانات. قد تستغرق العملية بضع دقائق.')
    54	                ->action(function () {
    55	                    $result = app(BackupService::class)->createBackup();
    56	
    57	                    if ($result['success']) {
    58	                        Notification::make()
    59	                            ->title('تم إنشاء النسخة الاحتياطية بنجاح')
    60	                            ->body("الملف: {$result['filename']}")
    61	                            ->success()
    62	                            ->send();
    63	                    } else {
    64	                        Notification::make()
    65	                            ->title('فشل إنشاء النسخة الاحتياطية')
    66	                            ->body($result['error'] ?? 'خطأ غير معروف')
    67	                            ->danger()
    68	                            ->send();
    69	                    }
    70	
    71	                    $this->refreshBackups();
    72	                }),
    73	
    74	            Action::make('cleanOld')
    75	                ->label('تنظيف القديمة')
    76	                ->icon('heroicon-o-trash')
    77	                ->color('danger')
    78	                ->requiresConfirmation()
    79	                ->modalHeading('تنظيف النسخ الاحتياطية القديمة')
    80	                ->modalDescription('سيتم الاحتفاظ بآخر 10 نسخ وحذف الباقي.')
    81	                ->action(function () {
    82	                    $count = app(BackupService::class)->cleanOldBackups(10);
    83	                    Notification::make()
    84	                        ->title("تم حذف {$count} نسخة احتياطية قديمة")
    85	                        ->success()
    86	                        ->send();
    87	
    88	                    $this->refreshBackups();
    89	                }),
    90	        ];
    91	    }
    92	
    93	    public function deleteBackup(string $filename): void
    94	    {
    95	        try {
    96	            app(BackupService::class)->deleteBackup($filename);
    97	            Notification::make()->title('تم حذف النسخة الاحتياطية')->success()->send();
    98	        } catch (\Throwable $e) {
    99	            Notification::make()->title($e->getMessage())->danger()->send();
   100	        }
   101	
   102	        $this->refreshBackups();
   103	    }
   104	
   105	    public function downloadBackup(string $filename)
   106	    {
   107	        try {
   108	            $path = app(BackupService::class)->downloadPath($filename);
   109	            return response()->download($path, $filename);
   110	        } catch (\Throwable $e) {
   111	            Notification::make()->title($e->getMessage())->danger()->send();
   112	        }
   113	    }
   114	
   115	    // HasTable is for future implementation if backups are stored in DB
   116	    // For now, we use a simple Livewire table in the blade view
   117	    public function table(Table $table): Table
   118	    {
   119	        // Placeholder — not used since backups are file-based
   120	        return $table->query(\App\Models\SystemSetting::query()->whereRaw('1=0'))
   121	            ->columns([]);
   122	    }
   123	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [071]: app/Filament/Pages/BackupManagement.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [072/494]: app/Filament/Pages/CardPreview.php
│ LANGUAGE: php | LINES: 91 | SIZE: 2717 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Filament\Pages;
     4	
     5	use App\Enums\CardStatus;
     6	use App\Models\MemberCard;
     7	use App\Services\Cards\CardService;
     8	use Filament\Actions;
     9	use Filament\Notifications\Notification;
    10	use Filament\Pages\Page;
    11	use Illuminate\Contracts\Support\Htmlable;
    12	
    13	class CardPreview extends Page
    14	{
    15	    protected static ?string $navigationIcon = 'heroicon-o-printer';
    16	
    17	    protected static ?string $navigationGroup = 'البطاقات والمستندات';
    18	
    19	    protected static ?int $navigationSort = 5;
    20	
    21	    protected static ?string $title = 'معاينة طباعة الكارنيه';
    22	
    23	    protected static ?string $navigationLabel = 'طباعة الكارنيه';
    24	
    25	    protected static ?string $slug = 'card-preview';
    26	
    27	    protected static string $view = 'filament.pages.card-preview';
    28	
    29	    protected static bool $shouldRegisterNavigation = true;
    30	
    31	    public ?int $cardId = null;
    32	    public ?array $cardData = null;
    33	    public ?array $eligibilityErrors = [];
    34	    public bool $isEligible = false;
    35	
    36	    public function mount(): void
    37	    {
    38	        $this->cardId = request()->query('card', null);
    39	
    40	        if ($this->cardId) {
    41	            $this->loadCardData();
    42	        }
    43	    }
    44	
    45	    public function loadCardData(): void
    46	    {
    47	        $card = MemberCard::with(['member.membershipType', 'member.status'])->find($this->cardId);
    48	
    49	        if (!$card) {
    50	            Notification::make()->title('الكارنيه غير موجود')->danger()->send();
    51	            return;
    52	        }
    53	
    54	        $cardService = app(CardService::class);
    55	        $this->cardData = $cardService->getCardPrintData($card);
    56	
    57	        // Check eligibility
    58	        $eligibility = $cardService->canPrintCard($card->member);
    59	        $this->isEligible = $eligibility['eligible'];
    60	        $this->eligibilityErrors = $eligibility['errors'];
    61	    }
    62	
    63	    public function printCard(): void
    64	    {
    65	        if (!$this->cardId) {
    66	            return;
    67	        }
    68	
    69	        $card = MemberCard::find($this->cardId);
    70	        if ($card && in_array($card->status, [CardStatus::ACTIVE->value])) {
    71	            app(CardService::class)->markAsPrinted($card);
    72	            Notification::make()->title('تم تسجيل الطباعة بنجاح')->success()->send();
    73	            $this->loadCardData();
    74	        }
    75	    }
    76	
    77	    public function getTitle(): string|Htmlable
    78	    {
    79	        return 'معاينة طباعة الكارنيه';
    80	    }
    81	
    82	    protected function getHeaderActions(): array
    83	    {
    84	        return [
    85	            Actions\Action::make('back_to_cards')
    86	                ->label('العودة للكروت')
    87	                ->icon('heroicon-o-arrow-right')
    88	                ->url(route('filament.admin.resources.member-cards.index'))
    89	                ->color('gray'),
    90	        ];
    91	    }
    92	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [072]: app/Filament/Pages/CardPreview.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [073/494]: app/Filament/Pages/Dashboard.php
│ LANGUAGE: php | LINES: 52 | SIZE: 1567 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Filament\Pages;
     4	
     5	use App\Filament\Widgets\MembershipGrowthChart;
     6	use App\Filament\Widgets\MembershipStatusChart;
     7	use App\Filament\Widgets\OverdueInstallmentsWidget;
     8	use App\Filament\Widgets\PendingActionsWidget;
     9	use App\Filament\Widgets\RecentMembersWidget;
    10	use App\Filament\Widgets\RevenueByCategoryChart;
    11	use App\Filament\Widgets\RevenueChart;
    12	use App\Filament\Widgets\StatsOverviewWidget;
    13	use App\Filament\Widgets\UpcomingExpirationsWidget;
    14	use App\Filament\Widgets\ViolationsOverviewWidget;
    15	use Filament\Pages\Dashboard as BaseDashboard;
    16	
    17	class Dashboard extends BaseDashboard
    18	{
    19	    protected static ?string $navigationIcon = 'heroicon-o-home';
    20	
    21	    protected static ?string $navigationGroup = 'الرئيسية';
    22	
    23	    protected static ?string $title = 'لوحة التحكم';
    24	
    25	    protected static ?int $navigationSort = 1;
    26	
    27	    public function getWidgets(): array
    28	    {
    29	        return [
    30	            StatsOverviewWidget::class,
    31	            MembershipGrowthChart::class,
    32	            RevenueChart::class,
    33	            MembershipStatusChart::class,
    34	            RevenueByCategoryChart::class,
    35	            PendingActionsWidget::class,
    36	            RecentMembersWidget::class,
    37	            UpcomingExpirationsWidget::class,
    38	            ViolationsOverviewWidget::class,
    39	            OverdueInstallmentsWidget::class,
    40	        ];
    41	    }
    42	
    43	    public function getColumns(): int | string | array
    44	    {
    45	        return [
    46	            'default' => 1,
    47	            'sm' => 1,
    48	            'md' => 2,
    49	            'lg' => 2,
    50	            'xl' => 2,
    51	        ];
    52	    }
    53	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [073]: app/Filament/Pages/Dashboard.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [074/494]: app/Filament/Pages/DocumentChecklist.php
│ LANGUAGE: php | LINES: 139 | SIZE: 5043 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Filament\Pages;
     4	
     5	use App\Models\Member;
     6	use App\Models\MemberDocument;
     7	use App\Services\Documents\DocumentService;
     8	use Filament\Actions;
     9	use Filament\Forms;
    10	use Filament\Forms\Concerns\InteractsWithForms;
    11	use Filament\Forms\Contracts\HasForms;
    12	use Filament\Forms\Form;
    13	use Filament\Notifications\Notification;
    14	use Filament\Pages\Page;
    15	use Illuminate\Contracts\Support\Htmlable;
    16	use Illuminate\Http\UploadedFile;
    17	
    18	class DocumentChecklist extends Page implements HasForms
    19	{
    20	    use InteractsWithForms;
    21	
    22	    protected static ?string $navigationIcon = 'heroicon-o-clipboard-document-list';
    23	
    24	    protected static ?string $navigationGroup = 'البطاقات والمستندات';
    25	
    26	    protected static ?int $navigationSort = 6;
    27	
    28	    protected static ?string $title = 'قائمة مراجعة المستندات';
    29	
    30	    protected static ?string $navigationLabel = 'قائمة المراجعة';
    31	
    32	    protected static ?string $slug = 'document-checklist';
    33	
    34	    protected static string $view = 'filament.pages.document-checklist';
    35	
    36	    public ?int $selectedMemberId = null;
    37	    public ?string $selectedStage = null;
    38	    public ?array $checklist = null;
    39	    public ?array $memberInfo = null;
    40	
    41	    public function mount(): void
    42	    {
    43	        $memberId = request()->query('member', null);
    44	        if ($memberId) {
    45	            $this->selectedMemberId = (int) $memberId;
    46	            $this->loadChecklist();
    47	        }
    48	    }
    49	
    50	    public function form(Form $form): Form
    51	    {
    52	        return $form
    53	            ->schema([
    54	                Forms\Components\Grid::make(3)
    55	                    ->schema([
    56	                        Forms\Components\Select::make('selectedMemberId')
    57	                            ->label('اختر العضو')
    58	                            ->options(function () {
    59	                                return Member::where('is_archived', false)
    60	                                    ->limit(100)
    61	                                    ->get()
    62	                                    ->mapWithKeys(fn ($m) => [$m->id => "{$m->membership_number} - {$m->full_name_ar}"])
    63	                                    ->toArray();
    64	                            })
    65	                            ->searchable()
    66	                            ->live()
    67	                            ->afterStateUpdated(fn () => $this->loadChecklist()),
    68	
    69	                        Forms\Components\Select::make('selectedStage')
    70	                            ->label('مرحلة العمل')
    71	                            ->options([
    72	                                '' => 'تلقائي',
    73	                                'application' => 'تقديم الطلب',
    74	                                'review' => 'المراجعة',
    75	                                'interview' => 'المقابلة',
    76	                                'board_approval' => 'موافقة المجلس',
    77	                                'payment' => 'الدفع',
    78	                                'active' => 'العضوية النشطة',
    79	                                'renewal' => 'التجديد',
    80	                                'transfer' => 'النقل',
    81	                            ])
    82	                            ->live()
    83	                            ->afterStateUpdated(fn () => $this->loadChecklist()),
    84	                    ]),
    85	            ]);
    86	    }
    87	
    88	    public function loadChecklist(): void
    89	    {
    90	        if (!$this->selectedMemberId) {
    91	            $this->checklist = null;
    92	            $this->memberInfo = null;
    93	            return;
    94	        }
    95	
    96	        $member = Member::with(['membershipType', 'status'])->find($this->selectedMemberId);
    97	        if (!$member) {
    98	            $this->checklist = null;
    99	            return;
   100	        }
   101	
   102	        $this->memberInfo = [
   103	            'id' => $member->id,
   104	            'name' => $member->full_name_ar,
   105	            'membership_number' => $member->membership_number,
   106	            'type' => $member->membershipType->name_ar ?? '',
   107	            'status' => $member->status->name_ar ?? '',
   108	        ];
   109	
   110	        $documentService = app(DocumentService::class);
   111	        $stage = !empty($this->selectedStage) ? $this->selectedStage : null;
   112	        $this->checklist = $documentService->getDocumentChecklist($member, $stage);
   113	    }
   114	
   115	    public function verifyDocument(int $documentId): void
   116	    {
   117	        $document = MemberDocument::find($documentId);
   118	        if ($document) {
   119	            app(DocumentService::class)->verifyDocument($document);
   120	            Notification::make()->title('تم التحقق من المستند')->success()->send();
   121	            $this->loadChecklist();
   122	        }
   123	    }
   124	
   125	    public function getTitle(): string|Htmlable
   126	    {
   127	        return 'قائمة مراجعة المستندات';
   128	    }
   129	
   130	    protected function getHeaderActions(): array
   131	    {
   132	        return [
   133	            Actions\Action::make('back_to_documents')
   134	                ->label('العودة للمستندات')
   135	                ->icon('heroicon-o-arrow-right')
   136	                ->url(route('filament.admin.resources.member-documents.index'))
   137	                ->color('gray'),
   138	        ];
   139	    }
   140	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [074]: app/Filament/Pages/DocumentChecklist.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [075/494]: app/Filament/Pages/Reports/AgingReport.php
│ LANGUAGE: php | LINES: 125 | SIZE: 4558 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Filament\Pages\Reports;
     4	
     5	use App\Services\Reporting\FinancialReportService;
     6	use Filament\Pages\Page;
     7	use Filament\Tables;
     8	use Filament\Tables\Table;
     9	use Filament\Tables\Concerns\InteractsWithTable;
    10	use Filament\Tables\Contracts\HasTable;
    11	use Filament\Forms\Concerns\InteractsWithForms;
    12	use Filament\Forms\Contracts\HasForms;
    13	use Barryvdh\DomPDF\Facade\Pdf;
    14	
    15	class AgingReport extends Page implements HasTable, HasForms
    16	{
    17	    use InteractsWithTable;
    18	    use InteractsWithForms;
    19	
    20	    protected static ?string $navigationIcon = 'heroicon-o-clock';
    21	
    22	    protected static ?string $navigationGroup = 'التقارير';
    23	
    24	    protected static ?string $navigationLabel = 'تقرير أعمار الديون';
    25	
    26	    protected static ?string $title = 'تقرير أعمار الديون';
    27	
    28	    protected static ?int $navigationSort = 5;
    29	
    30	    protected static string $view = 'filament.pages.reports.aging-report';
    31	
    32	    public array $agingBrackets = [];
    33	
    34	    public function mount(): void
    35	    {
    36	        $service = app(FinancialReportService::class);
    37	        $this->agingBrackets = $service->getAgingReport();
    38	    }
    39	
    40	    public function table(Table $table): Table
    41	    {
    42	        $service = app(FinancialReportService::class);
    43	
    44	        return $table
    45	            ->query($service->getOutstandingBalancesReport())
    46	            ->columns([
    47	                Tables\Columns\TextColumn::make('receipt_number')
    48	                    ->label('رقم الإيصال')
    49	                    ->sortable()
    50	                    ->searchable()
    51	                    ->badge()
    52	                    ->color('primary'),
    53	
    54	                Tables\Columns\TextColumn::make('member.full_name_ar')
    55	                    ->label('العضو')
    56	                    ->searchable()
    57	                    ->limit(30),
    58	
    59	                Tables\Columns\TextColumn::make('member.membership_number')
    60	                    ->label('رقم العضوية')
    61	                    ->badge()
    62	                    ->color('gray'),
    63	
    64	                Tables\Columns\TextColumn::make('receipt_date')
    65	                    ->label('تاريخ الإيصال')
    66	                    ->date('d/m/Y')
    67	                    ->sortable(),
    68	
    69	                Tables\Columns\TextColumn::make('total_amount')
    70	                    ->label('المبلغ المستحق')
    71	                    ->money('EGP')
    72	                    ->sortable()
    73	                    ->color('danger')
    74	                    ->summarize(Tables\Columns\Summarizers\Sum::make()->money('EGP')->label('الإجمالي')),
    75	
    76	                Tables\Columns\TextColumn::make('days_outstanding')
    77	                    ->label('أيام التأخير')
    78	                    ->state(fn ($record) => now()->diffInDays($record->receipt_date))
    79	                    ->sortable(query: fn ($query, $direction) => $query->orderBy('receipt_date', $direction === 'asc' ? 'desc' : 'asc'))
    80	                    ->badge()
    81	                    ->color(fn ($state) => match (true) {
    82	                        $state > 120 => 'danger',
    83	                        $state > 90 => 'danger',
    84	                        $state > 60 => 'warning',
    85	                        $state > 30 => 'warning',
    86	                        default => 'info',
    87	                    }),
    88	
    89	                Tables\Columns\TextColumn::make('status')
    90	                    ->label('الحالة')
    91	                    ->badge()
    92	                    ->formatStateUsing(fn ($state) => $state?->getLabel())
    93	                    ->color(fn ($state) => $state?->getColor()),
    94	            ])
    95	            ->defaultSort('receipt_date', 'asc')
    96	            ->striped()
    97	            ->paginated([10, 25, 50, 100])
    98	            ->defaultPaginationPageOption(25)
    99	            ->headerActions([
   100	                Tables\Actions\Action::make('export_pdf')
   101	                    ->label('تصدير PDF')
   102	                    ->icon('heroicon-o-document-arrow-down')
   103	                    ->color('danger')
   104	                    ->action(fn () => $this->exportPdf()),
   105	            ]);
   106	    }
   107	
   108	    public function exportPdf()
   109	    {
   110	        $service = app(FinancialReportService::class);
   111	        $receipts = $service->getOutstandingBalancesReport()->limit(500)->get();
   112	
   113	        $pdf = Pdf::loadView('reports.aging', [
   114	            'receipts' => $receipts,
   115	            'aging_brackets' => $this->agingBrackets,
   116	            'generated_at' => now(),
   117	        ]);
   118	
   119	        $pdf->setPaper('A4', 'landscape');
   120	
   121	        return response()->streamDownload(
   122	            fn () => print($pdf->output()),
   123	            'aging_report_' . now()->format('Y-m-d_His') . '.pdf'
   124	        );
   125	    }
   126	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [075]: app/Filament/Pages/Reports/AgingReport.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [076/494]: app/Filament/Pages/Reports/ChurnReport.php
│ LANGUAGE: php | LINES: 61 | SIZE: 1623 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Filament\Pages\Reports;
     4	
     5	use App\Services\Reporting\MembershipReportService;
     6	use Filament\Pages\Page;
     7	use Filament\Forms\Concerns\InteractsWithForms;
     8	use Filament\Forms\Contracts\HasForms;
     9	use Barryvdh\DomPDF\Facade\Pdf;
    10	
    11	class ChurnReport extends Page implements HasForms
    12	{
    13	    use InteractsWithForms;
    14	
    15	    protected static ?string $navigationIcon = 'heroicon-o-arrow-trending-down';
    16	
    17	    protected static ?string $navigationGroup = 'التقارير';
    18	
    19	    protected static ?string $navigationLabel = 'تقرير التسرب';
    20	
    21	    protected static ?string $title = 'تقرير تسرب الأعضاء';
    22	
    23	    protected static ?int $navigationSort = 8;
    24	
    25	    protected static string $view = 'filament.pages.reports.churn-report';
    26	
    27	    public array $churnData = [];
    28	    public int $months = 12;
    29	
    30	    public function mount(): void
    31	    {
    32	        $this->loadData();
    33	    }
    34	
    35	    public function loadData(): void
    36	    {
    37	        $service = app(MembershipReportService::class);
    38	        $this->churnData = $service->getChurnReport($this->months);
    39	    }
    40	
    41	    public function setMonths(int $months): void
    42	    {
    43	        $this->months = $months;
    44	        $this->loadData();
    45	    }
    46	
    47	    public function exportPdf()
    48	    {
    49	        $pdf = Pdf::loadView('reports.churn', [
    50	            'churn_data' => $this->churnData,
    51	            'months' => $this->months,
    52	            'generated_at' => now(),
    53	        ]);
    54	
    55	        $pdf->setPaper('A4', 'landscape');
    56	
    57	        return response()->streamDownload(
    58	            fn () => print($pdf->output()),
    59	            'churn_report_' . now()->format('Y-m-d_His') . '.pdf'
    60	        );
    61	    }
    62	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [076]: app/Filament/Pages/Reports/ChurnReport.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [077/494]: app/Filament/Pages/Reports/FinancialReport.php
│ LANGUAGE: php | LINES: 242 | SIZE: 8955 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Filament\Pages\Reports;
     4	
     5	use App\Enums\ReceiptStatus;
     6	use App\Enums\PaymentStatus;
     7	use App\Exports\FinancialExport;
     8	use App\Services\Reporting\FinancialReportService;
     9	use Filament\Forms;
    10	use Filament\Forms\Form;
    11	use Filament\Pages\Page;
    12	use Filament\Tables;
    13	use Filament\Tables\Table;
    14	use Filament\Tables\Concerns\InteractsWithTable;
    15	use Filament\Tables\Contracts\HasTable;
    16	use Filament\Forms\Concerns\InteractsWithForms;
    17	use Filament\Forms\Contracts\HasForms;
    18	use Maatwebsite\Excel\Facades\Excel;
    19	use Barryvdh\DomPDF\Facade\Pdf;
    20	use Illuminate\Support\Carbon;
    21	
    22	class FinancialReport extends Page implements HasTable, HasForms
    23	{
    24	    use InteractsWithTable;
    25	    use InteractsWithForms;
    26	
    27	    protected static ?string $navigationIcon = 'heroicon-o-banknotes';
    28	
    29	    protected static ?string $navigationGroup = 'التقارير';
    30	
    31	    protected static ?string $navigationLabel = 'التقرير المالي';
    32	
    33	    protected static ?string $title = 'التقرير المالي';
    34	
    35	    protected static ?int $navigationSort = 2;
    36	
    37	    protected static string $view = 'filament.pages.reports.financial-report';
    38	
    39	    public ?array $filters = [];
    40	
    41	    public array $collectionSummary = [];
    42	    public array $revenueByCategory = [];
    43	    public array $revenueByPaymentMethod = [];
    44	    public array $agingReport = [];
    45	
    46	    public function mount(): void
    47	    {
    48	        $this->filters = [
    49	            'date_from' => Carbon::now()->startOfMonth()->format('Y-m-d'),
    50	            'date_to' => Carbon::now()->endOfMonth()->format('Y-m-d'),
    51	        ];
    52	        $this->loadSummaryData();
    53	    }
    54	
    55	    protected function loadSummaryData(): void
    56	    {
    57	        $service = app(FinancialReportService::class);
    58	        $from = Carbon::parse($this->filters['date_from'] ?? Carbon::now()->startOfMonth());
    59	        $to = Carbon::parse($this->filters['date_to'] ?? Carbon::now()->endOfMonth());
    60	
    61	        $this->collectionSummary = $service->getCollectionSummary($from, $to);
    62	        $this->revenueByCategory = $service->getRevenueByCategory($from, $to);
    63	        $this->revenueByPaymentMethod = $service->getRevenueByPaymentMethod($from, $to);
    64	        $this->agingReport = $service->getAgingReport();
    65	    }
    66	
    67	    public function filtersForm(Form $form): Form
    68	    {
    69	        return $form
    70	            ->schema([
    71	                Forms\Components\Grid::make(4)
    72	                    ->schema([
    73	                        Forms\Components\Select::make('status')
    74	                            ->label('حالة الإيصال')
    75	                            ->options(ReceiptStatus::toFilamentOptions())
    76	                            ->placeholder('الكل'),
    77	
    78	                        Forms\Components\Select::make('payment_method')
    79	                            ->label('طريقة الدفع')
    80	                            ->options([
    81	                                'cash' => 'نقدي',
    82	                                'card' => 'بطاقة',
    83	                                'bank_transfer' => 'تحويل بنكي',
    84	                                'cheque' => 'شيك',
    85	                            ])
    86	                            ->placeholder('الكل'),
    87	
    88	                        Forms\Components\DatePicker::make('date_from')
    89	                            ->label('من تاريخ')
    90	                            ->native(false)
    91	                            ->displayFormat('d/m/Y')
    92	                            ->default(Carbon::now()->startOfMonth()),
    93	
    94	                        Forms\Components\DatePicker::make('date_to')
    95	                            ->label('إلى تاريخ')
    96	                            ->native(false)
    97	                            ->displayFormat('d/m/Y')
    98	                            ->default(Carbon::now()->endOfMonth()),
    99	
   100	                        Forms\Components\TextInput::make('amount_from')
   101	                            ->label('المبلغ من')
   102	                            ->numeric()
   103	                            ->prefix('ج.م'),
   104	
   105	                        Forms\Components\TextInput::make('amount_to')
   106	                            ->label('المبلغ إلى')
   107	                            ->numeric()
   108	                            ->prefix('ج.م'),
   109	                    ]),
   110	            ])
   111	            ->statePath('filters');
   112	    }
   113	
   114	    public function table(Table $table): Table
   115	    {
   116	        $service = app(FinancialReportService::class);
   117	
   118	        return $table
   119	            ->query($service->getReceiptsQuery($this->getFilterValues()))
   120	            ->columns([
   121	                Tables\Columns\TextColumn::make('receipt_number')
   122	                    ->label('رقم الإيصال')
   123	                    ->sortable()
   124	                    ->searchable()
   125	                    ->badge()
   126	                    ->color('primary'),
   127	
   128	                Tables\Columns\TextColumn::make('member.full_name_ar')
   129	                    ->label('العضو')
   130	                    ->searchable()
   131	                    ->limit(30),
   132	
   133	                Tables\Columns\TextColumn::make('member.membership_number')
   134	                    ->label('رقم العضوية')
   135	                    ->sortable()
   136	                    ->badge()
   137	                    ->color('gray'),
   138	
   139	                Tables\Columns\TextColumn::make('receipt_date')
   140	                    ->label('التاريخ')
   141	                    ->date('d/m/Y')
   142	                    ->sortable(),
   143	
   144	                Tables\Columns\TextColumn::make('total_amount')
   145	                    ->label('المبلغ الإجمالي')
   146	                    ->money('EGP')
   147	                    ->sortable()
   148	                    ->summarize(Tables\Columns\Summarizers\Sum::make()->money('EGP')->label('الإجمالي')),
   149	
   150	                Tables\Columns\TextColumn::make('discount_amount')
   151	                    ->label('الخصم')
   152	                    ->money('EGP')
   153	                    ->toggleable(isToggledHiddenByDefault: true),
   154	
   155	                Tables\Columns\TextColumn::make('tax_amount')
   156	                    ->label('الضريبة')
   157	                    ->money('EGP')
   158	                    ->toggleable(isToggledHiddenByDefault: true),
   159	
   160	                Tables\Columns\TextColumn::make('payment_method')
   161	                    ->label('طريقة الدفع')
   162	                    ->badge()
   163	                    ->color('info'),
   164	
   165	                Tables\Columns\TextColumn::make('status')
   166	                    ->label('الحالة')
   167	                    ->badge()
   168	                    ->formatStateUsing(fn ($state) => $state?->getLabel())
   169	                    ->color(fn ($state) => $state?->getColor()),
   170	
   171	                Tables\Columns\TextColumn::make('createdByUser.name')
   172	                    ->label('بواسطة')
   173	                    ->toggleable(isToggledHiddenByDefault: true),
   174	            ])
   175	            ->defaultSort('receipt_date', 'desc')
   176	            ->striped()
   177	            ->paginated([10, 25, 50, 100])
   178	            ->defaultPaginationPageOption(25)
   179	            ->headerActions([
   180	                Tables\Actions\Action::make('export_excel')
   181	                    ->label('تصدير Excel')
   182	                    ->icon('heroicon-o-arrow-down-tray')
   183	                    ->color('success')
   184	                    ->action(fn () => $this->exportExcel()),
   185	
   186	                Tables\Actions\Action::make('export_pdf')
   187	                    ->label('تصدير PDF')
   188	                    ->icon('heroicon-o-document-arrow-down')
   189	                    ->color('danger')
   190	                    ->action(fn () => $this->exportPdf()),
   191	            ]);
   192	    }
   193	
   194	    protected function getFilterValues(): array
   195	    {
   196	        return array_filter($this->filters ?? [], fn ($value) => $value !== null && $value !== '');
   197	    }
   198	
   199	    public function applyFilters(): void
   200	    {
   201	        $this->loadSummaryData();
   202	        $this->resetTable();
   203	    }
   204	
   205	    public function resetFilters(): void
   206	    {
   207	        $this->filters = [
   208	            'date_from' => Carbon::now()->startOfMonth()->format('Y-m-d'),
   209	            'date_to' => Carbon::now()->endOfMonth()->format('Y-m-d'),
   210	        ];
   211	        $this->loadSummaryData();
   212	        $this->resetTable();
   213	    }
   214	
   215	    public function exportExcel()
   216	    {
   217	        return Excel::download(
   218	            new FinancialExport($this->getFilterValues()),
   219	            'financial_report_' . now()->format('Y-m-d_His') . '.xlsx'
   220	        );
   221	    }
   222	
   223	    public function exportPdf()
   224	    {
   225	        $service = app(FinancialReportService::class);
   226	        $receipts = $service->getReceiptsQuery($this->getFilterValues())->limit(500)->get();
   227	
   228	        $pdf = Pdf::loadView('reports.financial', [
   229	            'receipts' => $receipts,
   230	            'filters' => $this->getFilterValues(),
   231	            'collection_summary' => $this->collectionSummary,
   232	            'revenue_by_category' => $this->revenueByCategory,
   233	            'generated_at' => now(),
   234	        ]);
   235	
   236	        $pdf->setPaper('A4', 'landscape');
   237	
   238	        return response()->streamDownload(
   239	            fn () => print($pdf->output()),
   240	            'financial_report_' . now()->format('Y-m-d_His') . '.pdf'
   241	        );
   242	    }
   243	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [077]: app/Filament/Pages/Reports/FinancialReport.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [078/494]: app/Filament/Pages/Reports/InstallmentReport.php
│ LANGUAGE: php | LINES: 160 | SIZE: 5697 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Filament\Pages\Reports;
     4	
     5	use App\Enums\InstallmentPlanStatus;
     6	use App\Services\Reporting\FinancialReportService;
     7	use Filament\Forms;
     8	use Filament\Forms\Form;
     9	use Filament\Pages\Page;
    10	use Filament\Tables;
    11	use Filament\Tables\Table;
    12	use Filament\Tables\Concerns\InteractsWithTable;
    13	use Filament\Tables\Contracts\HasTable;
    14	use Filament\Forms\Concerns\InteractsWithForms;
    15	use Filament\Forms\Contracts\HasForms;
    16	use Barryvdh\DomPDF\Facade\Pdf;
    17	
    18	class InstallmentReport extends Page implements HasTable, HasForms
    19	{
    20	    use InteractsWithTable;
    21	    use InteractsWithForms;
    22	
    23	    protected static ?string $navigationIcon = 'heroicon-o-calculator';
    24	
    25	    protected static ?string $navigationGroup = 'التقارير';
    26	
    27	    protected static ?string $navigationLabel = 'تقرير الأقساط';
    28	
    29	    protected static ?string $title = 'تقرير الأقساط';
    30	
    31	    protected static ?int $navigationSort = 6;
    32	
    33	    protected static string $view = 'filament.pages.reports.installment-report';
    34	
    35	    public ?array $filters = [];
    36	
    37	    public function filtersForm(Form $form): Form
    38	    {
    39	        return $form
    40	            ->schema([
    41	                Forms\Components\Grid::make(3)
    42	                    ->schema([
    43	                        Forms\Components\Select::make('status')
    44	                            ->label('حالة خطة الأقساط')
    45	                            ->options(InstallmentPlanStatus::toFilamentOptions())
    46	                            ->placeholder('الكل'),
    47	                    ]),
    48	            ])
    49	            ->statePath('filters');
    50	    }
    51	
    52	    public function table(Table $table): Table
    53	    {
    54	        $service = app(FinancialReportService::class);
    55	
    56	        return $table
    57	            ->query($service->getInstallmentReport($this->getFilterValues()))
    58	            ->columns([
    59	                Tables\Columns\TextColumn::make('member.membership_number')
    60	                    ->label('رقم العضوية')
    61	                    ->sortable()
    62	                    ->searchable()
    63	                    ->badge()
    64	                    ->color('primary'),
    65	
    66	                Tables\Columns\TextColumn::make('member.full_name_ar')
    67	                    ->label('العضو')
    68	                    ->searchable()
    69	                    ->limit(30),
    70	
    71	                Tables\Columns\TextColumn::make('total_amount')
    72	                    ->label('المبلغ الإجمالي')
    73	                    ->money('EGP')
    74	                    ->sortable(),
    75	
    76	                Tables\Columns\TextColumn::make('number_of_installments')
    77	                    ->label('عدد الأقساط')
    78	                    ->sortable(),
    79	
    80	                Tables\Columns\TextColumn::make('paid_installments')
    81	                    ->label('المسدد')
    82	                    ->state(fn ($record) => $record->installments->where('status', 'paid')->count())
    83	                    ->badge()
    84	                    ->color('success'),
    85	
    86	                Tables\Columns\TextColumn::make('remaining_installments')
    87	                    ->label('المتبقي')
    88	                    ->state(fn ($record) => $record->installments->where('status', 'pending')->count())
    89	                    ->badge()
    90	                    ->color(fn ($state) => $state > 0 ? 'warning' : 'success'),
    91	
    92	                Tables\Columns\TextColumn::make('total_paid')
    93	                    ->label('المبلغ المسدد')
    94	                    ->state(fn ($record) => $record->installments->where('status', 'paid')->sum('amount'))
    95	                    ->money('EGP')
    96	                    ->color('success'),
    97	
    98	                Tables\Columns\TextColumn::make('total_remaining')
    99	                    ->label('المبلغ المتبقي')
   100	                    ->state(fn ($record) => $record->installments->where('status', 'pending')->sum('amount'))
   101	                    ->money('EGP')
   102	                    ->color('danger'),
   103	
   104	                Tables\Columns\TextColumn::make('status')
   105	                    ->label('الحالة')
   106	                    ->badge()
   107	                    ->formatStateUsing(fn ($state) => $state?->getLabel())
   108	                    ->color(fn ($state) => $state?->getColor()),
   109	
   110	                Tables\Columns\TextColumn::make('created_at')
   111	                    ->label('تاريخ الإنشاء')
   112	                    ->date('d/m/Y')
   113	                    ->sortable(),
   114	            ])
   115	            ->defaultSort('created_at', 'desc')
   116	            ->striped()
   117	            ->paginated([10, 25, 50, 100])
   118	            ->defaultPaginationPageOption(25)
   119	            ->headerActions([
   120	                Tables\Actions\Action::make('export_pdf')
   121	                    ->label('تصدير PDF')
   122	                    ->icon('heroicon-o-document-arrow-down')
   123	                    ->color('danger')
   124	                    ->action(fn () => $this->exportPdf()),
   125	            ]);
   126	    }
   127	
   128	    protected function getFilterValues(): array
   129	    {
   130	        return array_filter($this->filters ?? [], fn ($value) => $value !== null && $value !== '');
   131	    }
   132	
   133	    public function applyFilters(): void
   134	    {
   135	        $this->resetTable();
   136	    }
   137	
   138	    public function resetFilters(): void
   139	    {
   140	        $this->filters = [];
   141	        $this->resetTable();
   142	    }
   143	
   144	    public function exportPdf()
   145	    {
   146	        $service = app(FinancialReportService::class);
   147	        $plans = $service->getInstallmentReport($this->getFilterValues())->limit(500)->get();
   148	
   149	        $pdf = Pdf::loadView('reports.installments', [
   150	            'plans' => $plans,
   151	            'generated_at' => now(),
   152	        ]);
   153	
   154	        $pdf->setPaper('A4', 'landscape');
   155	
   156	        return response()->streamDownload(
   157	            fn () => print($pdf->output()),
   158	            'installment_report_' . now()->format('Y-m-d_His') . '.pdf'
   159	        );
   160	    }
   161	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [078]: app/Filament/Pages/Reports/InstallmentReport.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [079/494]: app/Filament/Pages/Reports/MembershipReport.php
│ LANGUAGE: php | LINES: 249 | SIZE: 9012 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Filament\Pages\Reports;
     4	
     5	use App\Enums\Gender;
     6	use App\Enums\MembershipStatus;
     7	use App\Exports\MembersExport;
     8	use App\Models\MembershipType;
     9	use App\Services\Reporting\MembershipReportService;
    10	use Filament\Forms;
    11	use Filament\Forms\Form;
    12	use Filament\Pages\Page;
    13	use Filament\Tables;
    14	use Filament\Tables\Table;
    15	use Filament\Tables\Concerns\InteractsWithTable;
    16	use Filament\Tables\Contracts\HasTable;
    17	use Filament\Forms\Concerns\InteractsWithForms;
    18	use Filament\Forms\Contracts\HasForms;
    19	use Filament\Actions\Action;
    20	use Filament\Notifications\Notification;
    21	use Illuminate\Contracts\View\View;
    22	use Maatwebsite\Excel\Facades\Excel;
    23	use Barryvdh\DomPDF\Facade\Pdf;
    24	use Illuminate\Support\Carbon;
    25	
    26	class MembershipReport extends Page implements HasTable, HasForms
    27	{
    28	    use InteractsWithTable;
    29	    use InteractsWithForms;
    30	
    31	    protected static ?string $navigationIcon = 'heroicon-o-user-group';
    32	
    33	    protected static ?string $navigationGroup = 'التقارير';
    34	
    35	    protected static ?string $navigationLabel = 'تقرير العضويات';
    36	
    37	    protected static ?string $title = 'تقرير العضويات';
    38	
    39	    protected static ?int $navigationSort = 1;
    40	
    41	    protected static string $view = 'filament.pages.reports.membership-report';
    42	
    43	    public ?array $filters = [];
    44	
    45	    public ?string $membership_status = null;
    46	    public ?string $membership_type_id = null;
    47	    public ?string $gender = null;
    48	    public ?string $join_date_from = null;
    49	    public ?string $join_date_to = null;
    50	    public ?string $name = null;
    51	    public ?string $has_dependents = null;
    52	
    53	    public array $summaryByType = [];
    54	    public array $ageDistribution = [];
    55	    public array $genderDistribution = [];
    56	
    57	    public function mount(): void
    58	    {
    59	        $this->loadSummaryData();
    60	    }
    61	
    62	    protected function loadSummaryData(): void
    63	    {
    64	        $service = app(MembershipReportService::class);
    65	        $this->summaryByType = $service->getMembershipSummaryByType();
    66	        $this->ageDistribution = $service->getAgeDistribution();
    67	        $this->genderDistribution = $service->getGenderDistribution();
    68	    }
    69	
    70	    public function filtersForm(Form $form): Form
    71	    {
    72	        return $form
    73	            ->schema([
    74	                Forms\Components\Grid::make(4)
    75	                    ->schema([
    76	                        Forms\Components\Select::make('membership_status')
    77	                            ->label('حالة العضوية')
    78	                            ->options(MembershipStatus::toFilamentOptions())
    79	                            ->placeholder('الكل'),
    80	
    81	                        Forms\Components\Select::make('membership_type_id')
    82	                            ->label('نوع العضوية')
    83	                            ->options(MembershipType::pluck('name_ar', 'id'))
    84	                            ->placeholder('الكل'),
    85	
    86	                        Forms\Components\Select::make('gender')
    87	                            ->label('الجنس')
    88	                            ->options(Gender::toFilamentOptions())
    89	                            ->placeholder('الكل'),
    90	
    91	                        Forms\Components\Select::make('has_dependents')
    92	                            ->label('التابعين')
    93	                            ->options([
    94	                                'yes' => 'لديه تابعين',
    95	                                'no' => 'بدون تابعين',
    96	                            ])
    97	                            ->placeholder('الكل'),
    98	
    99	                        Forms\Components\DatePicker::make('join_date_from')
   100	                            ->label('تاريخ الانضمام من')
   101	                            ->native(false)
   102	                            ->displayFormat('d/m/Y'),
   103	
   104	                        Forms\Components\DatePicker::make('join_date_to')
   105	                            ->label('تاريخ الانضمام إلى')
   106	                            ->native(false)
   107	                            ->displayFormat('d/m/Y'),
   108	
   109	                        Forms\Components\TextInput::make('name')
   110	                            ->label('اسم العضو')
   111	                            ->placeholder('بحث بالاسم...'),
   112	                    ]),
   113	            ])
   114	            ->statePath('filters');
   115	    }
   116	
   117	    public function table(Table $table): Table
   118	    {
   119	        $service = app(MembershipReportService::class);
   120	
   121	        return $table
   122	            ->query($service->getMembersQuery($this->getFilterValues()))
   123	            ->columns([
   124	                Tables\Columns\TextColumn::make('membership_number')
   125	                    ->label('رقم العضوية')
   126	                    ->sortable()
   127	                    ->searchable()
   128	                    ->badge()
   129	                    ->color('primary'),
   130	
   131	                Tables\Columns\TextColumn::make('full_name_ar')
   132	                    ->label('الاسم بالعربية')
   133	                    ->sortable()
   134	                    ->searchable()
   135	                    ->limit(35),
   136	
   137	                Tables\Columns\TextColumn::make('full_name_en')
   138	                    ->label('الاسم بالإنجليزية')
   139	                    ->sortable()
   140	                    ->searchable()
   141	                    ->limit(35)
   142	                    ->toggleable(isToggledHiddenByDefault: true),
   143	
   144	                Tables\Columns\TextColumn::make('national_id')
   145	                    ->label('الرقم القومي')
   146	                    ->searchable()
   147	                    ->toggleable(isToggledHiddenByDefault: true),
   148	
   149	                Tables\Columns\TextColumn::make('membershipType.name_ar')
   150	                    ->label('نوع العضوية')
   151	                    ->badge()
   152	                    ->color('info'),
   153	
   154	                Tables\Columns\TextColumn::make('membership_status')
   155	                    ->label('الحالة')
   156	                    ->badge()
   157	                    ->formatStateUsing(fn ($state) => $state?->getLabel())
   158	                    ->color(fn ($state) => $state?->getColor()),
   159	
   160	                Tables\Columns\TextColumn::make('gender')
   161	                    ->label('الجنس')
   162	                    ->formatStateUsing(fn ($state) => $state?->getLabel())
   163	                    ->toggleable(isToggledHiddenByDefault: true),
   164	
   165	                Tables\Columns\TextColumn::make('date_of_birth')
   166	                    ->label('تاريخ الميلاد')
   167	                    ->date('d/m/Y')
   168	                    ->toggleable(isToggledHiddenByDefault: true),
   169	
   170	                Tables\Columns\TextColumn::make('phone')
   171	                    ->label('الهاتف')
   172	                    ->searchable(),
   173	
   174	                Tables\Columns\TextColumn::make('join_date')
   175	                    ->label('تاريخ الانضمام')
   176	                    ->date('d/m/Y')
   177	                    ->sortable(),
   178	
   179	                Tables\Columns\TextColumn::make('dependents_count')
   180	                    ->label('التابعين')
   181	                    ->counts('dependents')
   182	                    ->badge()
   183	                    ->color('gray'),
   184	            ])
   185	            ->defaultSort('membership_number')
   186	            ->striped()
   187	            ->paginated([10, 25, 50, 100])
   188	            ->defaultPaginationPageOption(25)
   189	            ->headerActions([
   190	                Tables\Actions\Action::make('export_excel')
   191	                    ->label('تصدير Excel')
   192	                    ->icon('heroicon-o-arrow-down-tray')
   193	                    ->color('success')
   194	                    ->action(fn () => $this->exportExcel()),
   195	
   196	                Tables\Actions\Action::make('export_pdf')
   197	                    ->label('تصدير PDF')
   198	                    ->icon('heroicon-o-document-arrow-down')
   199	                    ->color('danger')
   200	                    ->action(fn () => $this->exportPdf()),
   201	            ]);
   202	    }
   203	
   204	    protected function getFilterValues(): array
   205	    {
   206	        return array_filter($this->filters ?? [], fn ($value) => $value !== null && $value !== '');
   207	    }
   208	
   209	    public function applyFilters(): void
   210	    {
   211	        $this->resetTable();
   212	    }
   213	
   214	    public function resetFilters(): void
   215	    {
   216	        $this->filters = [];
   217	        $this->resetTable();
   218	    }
   219	
   220	    public function exportExcel()
   221	    {
   222	        $filters = $this->getFilterValues();
   223	
   224	        return Excel::download(
   225	            new MembersExport($filters),
   226	            'membership_report_' . now()->format('Y-m-d_His') . '.xlsx'
   227	        );
   228	    }
   229	
   230	    public function exportPdf()
   231	    {
   232	        $service = app(MembershipReportService::class);
   233	        $members = $service->getMembersQuery($this->getFilterValues())->limit(500)->get();
   234	
   235	        $pdf = Pdf::loadView('reports.membership', [
   236	            'members' => $members,
   237	            'filters' => $this->getFilterValues(),
   238	            'generated_at' => now(),
   239	            'summary_by_type' => $this->summaryByType,
   240	        ]);
   241	
   242	        $pdf->setPaper('A4', 'landscape');
   243	        $pdf->setOption('defaultFont', 'Arial');
   244	
   245	        return response()->streamDownload(
   246	            fn () => print($pdf->output()),
   247	            'membership_report_' . now()->format('Y-m-d_His') . '.pdf'
   248	        );
   249	    }
   250	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [079]: app/Filament/Pages/Reports/MembershipReport.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [080/494]: app/Filament/Pages/Reports/SubscriptionReport.php
│ LANGUAGE: php | LINES: 199 | SIZE: 7140 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Filament\Pages\Reports;
     4	
     5	use App\Enums\SubscriptionStatus;
     6	use App\Exports\SubscriptionsExport;
     7	use App\Models\SubscriptionPlan;
     8	use App\Services\Reporting\SubscriptionReportService;
     9	use Filament\Forms;
    10	use Filament\Forms\Form;
    11	use Filament\Pages\Page;
    12	use Filament\Tables;
    13	use Filament\Tables\Table;
    14	use Filament\Tables\Concerns\InteractsWithTable;
    15	use Filament\Tables\Contracts\HasTable;
    16	use Filament\Forms\Concerns\InteractsWithForms;
    17	use Filament\Forms\Contracts\HasForms;
    18	use Maatwebsite\Excel\Facades\Excel;
    19	use Barryvdh\DomPDF\Facade\Pdf;
    20	use Illuminate\Support\Carbon;
    21	
    22	class SubscriptionReport extends Page implements HasTable, HasForms
    23	{
    24	    use InteractsWithTable;
    25	    use InteractsWithForms;
    26	
    27	    protected static ?string $navigationIcon = 'heroicon-o-arrow-path';
    28	
    29	    protected static ?string $navigationGroup = 'التقارير';
    30	
    31	    protected static ?string $navigationLabel = 'تقرير الاشتراكات';
    32	
    33	    protected static ?string $title = 'تقرير الاشتراكات';
    34	
    35	    protected static ?int $navigationSort = 3;
    36	
    37	    protected static string $view = 'filament.pages.reports.subscription-report';
    38	
    39	    public ?array $filters = [];
    40	
    41	    public array $summaryByPlan = [];
    42	    public array $renewalRate = [];
    43	
    44	    public function mount(): void
    45	    {
    46	        $this->loadSummaryData();
    47	    }
    48	
    49	    protected function loadSummaryData(): void
    50	    {
    51	        $service = app(SubscriptionReportService::class);
    52	        $this->summaryByPlan = $service->getSubscriptionSummaryByPlan();
    53	        $this->renewalRate = $service->getRenewalRate(12);
    54	    }
    55	
    56	    public function filtersForm(Form $form): Form
    57	    {
    58	        return $form
    59	            ->schema([
    60	                Forms\Components\Grid::make(4)
    61	                    ->schema([
    62	                        Forms\Components\Select::make('status')
    63	                            ->label('حالة الاشتراك')
    64	                            ->options(SubscriptionStatus::toFilamentOptions())
    65	                            ->placeholder('الكل'),
    66	
    67	                        Forms\Components\Select::make('subscription_plan_id')
    68	                            ->label('خطة الاشتراك')
    69	                            ->options(SubscriptionPlan::pluck('name_ar', 'id'))
    70	                            ->placeholder('الكل'),
    71	
    72	                        Forms\Components\DatePicker::make('end_date_from')
    73	                            ->label('تاريخ الانتهاء من')
    74	                            ->native(false)
    75	                            ->displayFormat('d/m/Y'),
    76	
    77	                        Forms\Components\DatePicker::make('end_date_to')
    78	                            ->label('تاريخ الانتهاء إلى')
    79	                            ->native(false)
    80	                            ->displayFormat('d/m/Y'),
    81	
    82	                        Forms\Components\TextInput::make('expiring_within_days')
    83	                            ->label('تنتهي خلال (أيام)')
    84	                            ->numeric()
    85	                            ->placeholder('مثال: 30'),
    86	                    ]),
    87	            ])
    88	            ->statePath('filters');
    89	    }
    90	
    91	    public function table(Table $table): Table
    92	    {
    93	        $service = app(SubscriptionReportService::class);
    94	
    95	        return $table
    96	            ->query($service->getSubscriptionsQuery($this->getFilterValues()))
    97	            ->columns([
    98	                Tables\Columns\TextColumn::make('member.membership_number')
    99	                    ->label('رقم العضوية')
   100	                    ->sortable()
   101	                    ->searchable()
   102	                    ->badge()
   103	                    ->color('primary'),
   104	
   105	                Tables\Columns\TextColumn::make('member.full_name_ar')
   106	                    ->label('العضو')
   107	                    ->searchable()
   108	                    ->limit(30),
   109	
   110	                Tables\Columns\TextColumn::make('subscriptionPlan.name_ar')
   111	                    ->label('خطة الاشتراك')
   112	                    ->badge()
   113	                    ->color('info'),
   114	
   115	                Tables\Columns\TextColumn::make('start_date')
   116	                    ->label('بداية الاشتراك')
   117	                    ->date('d/m/Y')
   118	                    ->sortable(),
   119	
   120	                Tables\Columns\TextColumn::make('end_date')
   121	                    ->label('نهاية الاشتراك')
   122	                    ->date('d/m/Y')
   123	                    ->sortable()
   124	                    ->color(fn ($record) => $record->end_date?->isPast() ? 'danger' : ($record->end_date?->diffInDays(now()) <= 30 ? 'warning' : 'success')),
   125	
   126	                Tables\Columns\TextColumn::make('status')
   127	                    ->label('الحالة')
   128	                    ->badge()
   129	                    ->formatStateUsing(fn ($state) => $state?->getLabel())
   130	                    ->color(fn ($state) => $state?->getColor()),
   131	
   132	                Tables\Columns\TextColumn::make('amount')
   133	                    ->label('المبلغ')
   134	                    ->money('EGP')
   135	                    ->sortable()
   136	                    ->summarize(Tables\Columns\Summarizers\Sum::make()->money('EGP')->label('الإجمالي')),
   137	            ])
   138	            ->defaultSort('end_date', 'asc')
   139	            ->striped()
   140	            ->paginated([10, 25, 50, 100])
   141	            ->defaultPaginationPageOption(25)
   142	            ->headerActions([
   143	                Tables\Actions\Action::make('export_excel')
   144	                    ->label('تصدير Excel')
   145	                    ->icon('heroicon-o-arrow-down-tray')
   146	                    ->color('success')
   147	                    ->action(fn () => $this->exportExcel()),
   148	
   149	                Tables\Actions\Action::make('export_pdf')
   150	                    ->label('تصدير PDF')
   151	                    ->icon('heroicon-o-document-arrow-down')
   152	                    ->color('danger')
   153	                    ->action(fn () => $this->exportPdf()),
   154	            ]);
   155	    }
   156	
   157	    protected function getFilterValues(): array
   158	    {
   159	        return array_filter($this->filters ?? [], fn ($value) => $value !== null && $value !== '');
   160	    }
   161	
   162	    public function applyFilters(): void
   163	    {
   164	        $this->resetTable();
   165	    }
   166	
   167	    public function resetFilters(): void
   168	    {
   169	        $this->filters = [];
   170	        $this->resetTable();
   171	    }
   172	
   173	    public function exportExcel()
   174	    {
   175	        return Excel::download(
   176	            new SubscriptionsExport($this->getFilterValues()),
   177	            'subscription_report_' . now()->format('Y-m-d_His') . '.xlsx'
   178	        );
   179	    }
   180	
   181	    public function exportPdf()
   182	    {
   183	        $service = app(SubscriptionReportService::class);
   184	        $subscriptions = $service->getSubscriptionsQuery($this->getFilterValues())->limit(500)->get();
   185	
   186	        $pdf = Pdf::loadView('reports.subscriptions', [
   187	            'subscriptions' => $subscriptions,
   188	            'filters' => $this->getFilterValues(),
   189	            'summary_by_plan' => $this->summaryByPlan,
   190	            'generated_at' => now(),
   191	        ]);
   192	
   193	        $pdf->setPaper('A4', 'landscape');
   194	
   195	        return response()->streamDownload(
   196	            fn () => print($pdf->output()),
   197	            'subscription_report_' . now()->format('Y-m-d_His') . '.pdf'
   198	        );
   199	    }
   200	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [080]: app/Filament/Pages/Reports/SubscriptionReport.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [081/494]: app/Filament/Pages/Reports/TopPayingMembersReport.php
│ LANGUAGE: php | LINES: 100 | SIZE: 3115 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Filament\Pages\Reports;
     4	
     5	use App\Services\Reporting\FinancialReportService;
     6	use Filament\Forms;
     7	use Filament\Forms\Form;
     8	use Filament\Pages\Page;
     9	use Filament\Forms\Concerns\InteractsWithForms;
    10	use Filament\Forms\Contracts\HasForms;
    11	use Barryvdh\DomPDF\Facade\Pdf;
    12	use Illuminate\Support\Carbon;
    13	
    14	class TopPayingMembersReport extends Page implements HasForms
    15	{
    16	    use InteractsWithForms;
    17	
    18	    protected static ?string $navigationIcon = 'heroicon-o-trophy';
    19	
    20	    protected static ?string $navigationGroup = 'التقارير';
    21	
    22	    protected static ?string $navigationLabel = 'أعلى الأعضاء سداداً';
    23	
    24	    protected static ?string $title = 'أعلى الأعضاء سداداً';
    25	
    26	    protected static ?int $navigationSort = 7;
    27	
    28	    protected static string $view = 'filament.pages.reports.top-paying-members';
    29	
    30	    public ?string $date_from = null;
    31	    public ?string $date_to = null;
    32	    public int $limit = 20;
    33	
    34	    public array $topMembers = [];
    35	
    36	    public function mount(): void
    37	    {
    38	        $this->date_from = Carbon::now()->startOfYear()->format('Y-m-d');
    39	        $this->date_to = Carbon::now()->endOfYear()->format('Y-m-d');
    40	        $this->loadData();
    41	    }
    42	
    43	    public function form(Form $form): Form
    44	    {
    45	        return $form
    46	            ->schema([
    47	                Forms\Components\Grid::make(3)
    48	                    ->schema([
    49	                        Forms\Components\DatePicker::make('date_from')
    50	                            ->label('من تاريخ')
    51	                            ->native(false)
    52	                            ->displayFormat('d/m/Y')
    53	                            ->required(),
    54	
    55	                        Forms\Components\DatePicker::make('date_to')
    56	                            ->label('إلى تاريخ')
    57	                            ->native(false)
    58	                            ->displayFormat('d/m/Y')
    59	                            ->required(),
    60	
    61	                        Forms\Components\TextInput::make('limit')
    62	                            ->label('عدد النتائج')
    63	                            ->numeric()
    64	                            ->default(20)
    65	                            ->minValue(5)
    66	                            ->maxValue(100),
    67	                    ]),
    68	            ]);
    69	    }
    70	
    71	    public function loadData(): void
    72	    {
    73	        $service = app(FinancialReportService::class);
    74	        $from = Carbon::parse($this->date_from);
    75	        $to = Carbon::parse($this->date_to);
    76	
    77	        $this->topMembers = $service->getTopPayingMembers($from, $to, $this->limit);
    78	    }
    79	
    80	    public function applyFilters(): void
    81	    {
    82	        $this->loadData();
    83	    }
    84	
    85	    public function exportPdf()
    86	    {
    87	        $pdf = Pdf::loadView('reports.top-paying-members', [
    88	            'members' => $this->topMembers,
    89	            'date_from' => $this->date_from,
    90	            'date_to' => $this->date_to,
    91	            'generated_at' => now(),
    92	        ]);
    93	
    94	        $pdf->setPaper('A4', 'portrait');
    95	
    96	        return response()->streamDownload(
    97	            fn () => print($pdf->output()),
    98	            'top_paying_members_' . now()->format('Y-m-d_His') . '.pdf'
    99	        );
   100	    }
   101	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [081]: app/Filament/Pages/Reports/TopPayingMembersReport.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [082/494]: app/Filament/Pages/Reports/ViolationsReport.php
│ LANGUAGE: php | LINES: 223 | SIZE: 8152 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Filament\Pages\Reports;
     4	
     5	use App\Enums\ViolationStatus;
     6	use App\Enums\ViolationSeverity;
     7	use App\Exports\ViolationsExport;
     8	use App\Models\ViolationType;
     9	use App\Services\Reporting\ViolationReportService;
    10	use Filament\Forms;
    11	use Filament\Forms\Form;
    12	use Filament\Pages\Page;
    13	use Filament\Tables;
    14	use Filament\Tables\Table;
    15	use Filament\Tables\Concerns\InteractsWithTable;
    16	use Filament\Tables\Contracts\HasTable;
    17	use Filament\Forms\Concerns\InteractsWithForms;
    18	use Filament\Forms\Contracts\HasForms;
    19	use Maatwebsite\Excel\Facades\Excel;
    20	use Barryvdh\DomPDF\Facade\Pdf;
    21	use Illuminate\Support\Carbon;
    22	
    23	class ViolationsReport extends Page implements HasTable, HasForms
    24	{
    25	    use InteractsWithTable;
    26	    use InteractsWithForms;
    27	
    28	    protected static ?string $navigationIcon = 'heroicon-o-flag';
    29	
    30	    protected static ?string $navigationGroup = 'التقارير';
    31	
    32	    protected static ?string $navigationLabel = 'تقرير المخالفات';
    33	
    34	    protected static ?string $title = 'تقرير المخالفات';
    35	
    36	    protected static ?int $navigationSort = 4;
    37	
    38	    protected static string $view = 'filament.pages.reports.violations-report';
    39	
    40	    public ?array $filters = [];
    41	
    42	    public array $byType = [];
    43	    public array $bySeverity = [];
    44	    public array $monthlyTrend = [];
    45	    public array $repeatOffenders = [];
    46	    public array $penaltySummary = [];
    47	
    48	    public function mount(): void
    49	    {
    50	        $this->filters = [
    51	            'date_from' => Carbon::now()->startOfYear()->format('Y-m-d'),
    52	            'date_to' => Carbon::now()->endOfYear()->format('Y-m-d'),
    53	        ];
    54	        $this->loadSummaryData();
    55	    }
    56	
    57	    protected function loadSummaryData(): void
    58	    {
    59	        $service = app(ViolationReportService::class);
    60	        $from = Carbon::parse($this->filters['date_from'] ?? Carbon::now()->startOfYear());
    61	        $to = Carbon::parse($this->filters['date_to'] ?? Carbon::now()->endOfYear());
    62	
    63	        $this->byType = $service->getViolationsByTypeReport($from, $to);
    64	        $this->bySeverity = $service->getViolationsBySeverityReport($from, $to);
    65	        $this->monthlyTrend = $service->getMonthlyViolationTrend(12);
    66	        $this->repeatOffenders = $service->getRepeatOffendersReport(2);
    67	        $this->penaltySummary = $service->getPenaltySummary($from, $to);
    68	    }
    69	
    70	    public function filtersForm(Form $form): Form
    71	    {
    72	        return $form
    73	            ->schema([
    74	                Forms\Components\Grid::make(4)
    75	                    ->schema([
    76	                        Forms\Components\Select::make('status')
    77	                            ->label('حالة المخالفة')
    78	                            ->options(ViolationStatus::toFilamentOptions())
    79	                            ->placeholder('الكل'),
    80	
    81	                        Forms\Components\Select::make('severity')
    82	                            ->label('الخطورة')
    83	                            ->options(ViolationSeverity::toFilamentOptions())
    84	                            ->placeholder('الكل'),
    85	
    86	                        Forms\Components\Select::make('violation_type_id')
    87	                            ->label('نوع المخالفة')
    88	                            ->options(ViolationType::pluck('name_ar', 'id'))
    89	                            ->placeholder('الكل')
    90	                            ->searchable(),
    91	
    92	                        Forms\Components\DatePicker::make('date_from')
    93	                            ->label('من تاريخ')
    94	                            ->native(false)
    95	                            ->displayFormat('d/m/Y')
    96	                            ->default(Carbon::now()->startOfYear()),
    97	
    98	                        Forms\Components\DatePicker::make('date_to')
    99	                            ->label('إلى تاريخ')
   100	                            ->native(false)
   101	                            ->displayFormat('d/m/Y')
   102	                            ->default(Carbon::now()->endOfYear()),
   103	                    ]),
   104	            ])
   105	            ->statePath('filters');
   106	    }
   107	
   108	    public function table(Table $table): Table
   109	    {
   110	        $service = app(ViolationReportService::class);
   111	
   112	        return $table
   113	            ->query($service->getViolationsQuery($this->getFilterValues()))
   114	            ->columns([
   115	                Tables\Columns\TextColumn::make('id')
   116	                    ->label('#')
   117	                    ->sortable(),
   118	
   119	                Tables\Columns\TextColumn::make('member.full_name_ar')
   120	                    ->label('العضو')
   121	                    ->searchable()
   122	                    ->limit(30),
   123	
   124	                Tables\Columns\TextColumn::make('member.membership_number')
   125	                    ->label('رقم العضوية')
   126	                    ->badge()
   127	                    ->color('primary'),
   128	
   129	                Tables\Columns\TextColumn::make('violationType.name_ar')
   130	                    ->label('نوع المخالفة')
   131	                    ->badge()
   132	                    ->color('warning'),
   133	
   134	                Tables\Columns\TextColumn::make('severity')
   135	                    ->label('الخطورة')
   136	                    ->badge()
   137	                    ->formatStateUsing(fn ($state) => $state?->getLabel())
   138	                    ->color(fn ($state) => $state?->getColor()),
   139	
   140	                Tables\Columns\TextColumn::make('status')
   141	                    ->label('الحالة')
   142	                    ->badge()
   143	                    ->formatStateUsing(fn ($state) => $state?->getLabel())
   144	                    ->color(fn ($state) => $state?->getColor()),
   145	
   146	                Tables\Columns\TextColumn::make('violation_date')
   147	                    ->label('تاريخ المخالفة')
   148	                    ->date('d/m/Y')
   149	                    ->sortable(),
   150	
   151	                Tables\Columns\TextColumn::make('reportedByUser.name')
   152	                    ->label('المبلّغ')
   153	                    ->toggleable(isToggledHiddenByDefault: true),
   154	            ])
   155	            ->defaultSort('violation_date', 'desc')
   156	            ->striped()
   157	            ->paginated([10, 25, 50, 100])
   158	            ->defaultPaginationPageOption(25)
   159	            ->headerActions([
   160	                Tables\Actions\Action::make('export_excel')
   161	                    ->label('تصدير Excel')
   162	                    ->icon('heroicon-o-arrow-down-tray')
   163	                    ->color('success')
   164	                    ->action(fn () => $this->exportExcel()),
   165	
   166	                Tables\Actions\Action::make('export_pdf')
   167	                    ->label('تصدير PDF')
   168	                    ->icon('heroicon-o-document-arrow-down')
   169	                    ->color('danger')
   170	                    ->action(fn () => $this->exportPdf()),
   171	            ]);
   172	    }
   173	
   174	    protected function getFilterValues(): array
   175	    {
   176	        return array_filter($this->filters ?? [], fn ($value) => $value !== null && $value !== '');
   177	    }
   178	
   179	    public function applyFilters(): void
   180	    {
   181	        $this->loadSummaryData();
   182	        $this->resetTable();
   183	    }
   184	
   185	    public function resetFilters(): void
   186	    {
   187	        $this->filters = [
   188	            'date_from' => Carbon::now()->startOfYear()->format('Y-m-d'),
   189	            'date_to' => Carbon::now()->endOfYear()->format('Y-m-d'),
   190	        ];
   191	        $this->loadSummaryData();
   192	        $this->resetTable();
   193	    }
   194	
   195	    public function exportExcel()
   196	    {
   197	        return Excel::download(
   198	            new ViolationsExport($this->getFilterValues()),
   199	            'violations_report_' . now()->format('Y-m-d_His') . '.xlsx'
   200	        );
   201	    }
   202	
   203	    public function exportPdf()
   204	    {
   205	        $service = app(ViolationReportService::class);
   206	        $violations = $service->getViolationsQuery($this->getFilterValues())->limit(500)->get();
   207	
   208	        $pdf = Pdf::loadView('reports.violations', [
   209	            'violations' => $violations,
   210	            'filters' => $this->getFilterValues(),
   211	            'by_type' => $this->byType,
   212	            'by_severity' => $this->bySeverity,
   213	            'penalty_summary' => $this->penaltySummary,
   214	            'generated_at' => now(),
   215	        ]);
   216	
   217	        $pdf->setPaper('A4', 'landscape');
   218	
   219	        return response()->streamDownload(
   220	            fn () => print($pdf->output()),
   221	            'violations_report_' . now()->format('Y-m-d_His') . '.pdf'
   222	        );
   223	    }
   224	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [082]: app/Filament/Pages/Reports/ViolationsReport.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [083/494]: app/Filament/Pages/SystemSettings.php
│ LANGUAGE: php | LINES: 195 | SIZE: 5773 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Filament\Pages;
     4	
     5	use App\Models\SystemSetting;
     6	use App\Services\Admin\SettingsService;
     7	use Filament\Pages\Page;
     8	use Filament\Forms;
     9	use Filament\Forms\Form;
    10	use Filament\Forms\Concerns\InteractsWithForms;
    11	use Filament\Forms\Contracts\HasForms;
    12	use Filament\Actions\Action;
    13	use Filament\Notifications\Notification;
    14	
    15	class SystemSettings extends Page implements HasForms
    16	{
    17	    use InteractsWithForms;
    18	
    19	    protected static ?string $navigationIcon = 'heroicon-o-cog-6-tooth';
    20	
    21	    protected static ?string $navigationGroup = 'إدارة النظام';
    22	
    23	    protected static ?string $navigationLabel = 'إعدادات النظام';
    24	
    25	    protected static ?string $title = 'إعدادات النظام';
    26	
    27	    protected static ?int $navigationSort = 2;
    28	
    29	    protected static string $view = 'filament.pages.system-settings';
    30	
    31	    public ?array $generalData = [];
    32	    public ?array $membershipData = [];
    33	    public ?array $financialData = [];
    34	    public ?array $notificationData = [];
    35	    public ?array $systemData = [];
    36	
    37	    public function mount(): void
    38	    {
    39	        $settings = app(SettingsService::class)->getAllCached();
    40	
    41	        $this->generalData = $this->getGroupValues($settings, 'general');
    42	        $this->membershipData = $this->getGroupValues($settings, 'membership');
    43	        $this->financialData = $this->getGroupValues($settings, 'financial');
    44	        $this->notificationData = $this->getGroupValues($settings, 'notification');
    45	        $this->systemData = $this->getGroupValues($settings, 'system');
    46	    }
    47	
    48	    protected function getGroupValues($settings, string $group): array
    49	    {
    50	        return $settings->where('group_key', $group)
    51	            ->pluck('setting_value', 'setting_key')
    52	            ->toArray();
    53	    }
    54	
    55	    protected function getForms(): array
    56	    {
    57	        return [
    58	            'generalForm',
    59	            'membershipForm',
    60	            'financialForm',
    61	            'notificationForm',
    62	            'systemForm',
    63	        ];
    64	    }
    65	
    66	    public function generalForm(Form $form): Form
    67	    {
    68	        return $form
    69	            ->schema($this->buildFormFieldsForGroup('general'))
    70	            ->statePath('generalData');
    71	    }
    72	
    73	    public function membershipForm(Form $form): Form
    74	    {
    75	        return $form
    76	            ->schema($this->buildFormFieldsForGroup('membership'))
    77	            ->statePath('membershipData');
    78	    }
    79	
    80	    public function financialForm(Form $form): Form
    81	    {
    82	        return $form
    83	            ->schema($this->buildFormFieldsForGroup('financial'))
    84	            ->statePath('financialData');
    85	    }
    86	
    87	    public function notificationForm(Form $form): Form
    88	    {
    89	        return $form
    90	            ->schema($this->buildFormFieldsForGroup('notification'))
    91	            ->statePath('notificationData');
    92	    }
    93	
    94	    public function systemForm(Form $form): Form
    95	    {
    96	        return $form
    97	            ->schema($this->buildFormFieldsForGroup('system'))
    98	            ->statePath('systemData');
    99	    }
   100	
   101	    protected function buildFormFieldsForGroup(string $group): array
   102	    {
   103	        $settings = SystemSetting::where('group_key', $group)
   104	            ->orderBy('display_order')
   105	            ->get();
   106	
   107	        $fields = [];
   108	
   109	        foreach ($settings as $setting) {
   110	            $field = match ($setting->value_type) {
   111	                'boolean' => Forms\Components\Toggle::make($setting->setting_key)
   112	                    ->label($setting->description_ar)
   113	                    ->helperText($setting->description_en),
   114	
   115	                'integer' => Forms\Components\TextInput::make($setting->setting_key)
   116	                    ->label($setting->description_ar)
   117	                    ->helperText($setting->description_en)
   118	                    ->numeric()
   119	                    ->integer(),
   120	
   121	                'float', 'decimal' => Forms\Components\TextInput::make($setting->setting_key)
   122	                    ->label($setting->description_ar)
   123	                    ->helperText($setting->description_en)
   124	                    ->numeric(),
   125	
   126	                'json', 'array' => Forms\Components\Textarea::make($setting->setting_key)
   127	                    ->label($setting->description_ar)
   128	                    ->helperText($setting->description_en)
   129	                    ->rows(3),
   130	
   131	                default => Forms\Components\TextInput::make($setting->setting_key)
   132	                    ->label($setting->description_ar)
   133	                    ->helperText($setting->description_en),
   134	            };
   135	
   136	            $fields[] = $field;
   137	        }
   138	
   139	        return $fields;
   140	    }
   141	
   142	    public function saveGeneral(): void
   143	    {
   144	        $this->saveGroup($this->generalData);
   145	    }
   146	
   147	    public function saveMembership(): void
   148	    {
   149	        $this->saveGroup($this->membershipData);
   150	    }
   151	
   152	    public function saveFinancial(): void
   153	    {
   154	        $this->saveGroup($this->financialData);
   155	    }
   156	
   157	    public function saveNotification(): void
   158	    {
   159	        $this->saveGroup($this->notificationData);
   160	    }
   161	
   162	    public function saveSystem(): void
   163	    {
   164	        $this->saveGroup($this->systemData);
   165	    }
   166	
   167	    protected function saveGroup(array $data): void
   168	    {
   169	        $service = app(SettingsService::class);
   170	
   171	        foreach ($data as $key => $value) {
   172	            try {
   173	                $service->set($key, $value);
   174	            } catch (\InvalidArgumentException $e) {
   175	                continue;
   176	            }
   177	        }
   178	
   179	        $service->clearCache();
   180	
   181	        Notification::make()
   182	            ->title('تم حفظ الإعدادات بنجاح')
   183	            ->success()
   184	            ->send();
   185	    }
   186	
   187	    public function clearSettingsCache(): void
   188	    {
   189	        app(SettingsService::class)->clearCache();
   190	
   191	        Notification::make()
   192	            ->title('تم مسح ذاكرة التخزين المؤقت للإعدادات')
   193	            ->success()
   194	            ->send();
   195	    }
   196	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [083]: app/Filament/Pages/SystemSettings.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [084/494]: app/Filament/Resources/ActivityLogResource.php
│ LANGUAGE: php | LINES: 186 | SIZE: 7490 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Filament\Resources;
     4	
     5	use App\Filament\Resources\ActivityLogResource\Pages;
     6	use Spatie\Activitylog\Models\Activity;
     7	use Filament\Forms;
     8	use Filament\Resources\Resource;
     9	use Filament\Tables;
    10	use Filament\Tables\Table;
    11	use Filament\Infolists;
    12	use Filament\Infolists\Infolist;
    13	use Illuminate\Database\Eloquent\Builder;
    14	
    15	class ActivityLogResource extends Resource
    16	{
    17	    protected static ?string $model = Activity::class;
    18	
    19	    protected static ?string $navigationIcon = 'heroicon-o-clipboard-document-check';
    20	
    21	    protected static ?string $navigationGroup = 'إدارة النظام';
    22	
    23	    protected static ?string $navigationLabel = 'سجل النشاط';
    24	
    25	    protected static ?string $modelLabel = 'سجل نشاط';
    26	
    27	    protected static ?string $pluralModelLabel = 'سجلات النشاط';
    28	
    29	    protected static ?int $navigationSort = 3;
    30	
    31	    public static function canCreate(): bool
    32	    {
    33	        return false;
    34	    }
    35	
    36	    public static function canEdit(\Illuminate\Database\Eloquent\Model $record): bool
    37	    {
    38	        return false;
    39	    }
    40	
    41	    public static function canDelete(\Illuminate\Database\Eloquent\Model $record): bool
    42	    {
    43	        return false;
    44	    }
    45	
    46	    public static function table(Table $table): Table
    47	    {
    48	        return $table
    49	            ->columns([
    50	                Tables\Columns\TextColumn::make('log_name')
    51	                    ->label('السجل')
    52	                    ->badge()
    53	                    ->color(fn (string $state) => match ($state) {
    54	                        'default' => 'gray',
    55	                        'settings' => 'warning',
    56	                        'board_offers' => 'info',
    57	                        'board_decisions' => 'success',
    58	                        'cash_registers' => 'danger',
    59	                        'members' => 'primary',
    60	                        default => 'gray',
    61	                    })
    62	                    ->searchable(),
    63	
    64	                Tables\Columns\TextColumn::make('description')
    65	                    ->label('الوصف')
    66	                    ->searchable()
    67	                    ->wrap()
    68	                    ->limit(80),
    69	
    70	                Tables\Columns\TextColumn::make('subject_type')
    71	                    ->label('نوع الكائن')
    72	                    ->formatStateUsing(fn (?string $state) => $state ? class_basename($state) : '—')
    73	                    ->badge()
    74	                    ->color('info'),
    75	
    76	                Tables\Columns\TextColumn::make('subject_id')
    77	                    ->label('معرف الكائن')
    78	                    ->placeholder('—'),
    79	
    80	                Tables\Columns\TextColumn::make('causer.name')
    81	                    ->label('بواسطة')
    82	                    ->searchable()
    83	                    ->placeholder('النظام'),
    84	
    85	                Tables\Columns\TextColumn::make('created_at')
    86	                    ->label('التاريخ')
    87	                    ->dateTime('d/m/Y H:i:s')
    88	                    ->sortable(),
    89	            ])
    90	            ->defaultSort('created_at', 'desc')
    91	            ->filters([
    92	                Tables\Filters\SelectFilter::make('log_name')
    93	                    ->label('السجل')
    94	                    ->options(fn () => Activity::distinct()->pluck('log_name', 'log_name')->toArray()),
    95	
    96	                Tables\Filters\SelectFilter::make('subject_type')
    97	                    ->label('نوع الكائن')
    98	                    ->options(fn () => Activity::distinct()
    99	                        ->whereNotNull('subject_type')
   100	                        ->pluck('subject_type')
   101	                        ->mapWithKeys(fn ($v) => [$v => class_basename($v)])
   102	                        ->toArray()),
   103	
   104	                Tables\Filters\SelectFilter::make('causer_id')
   105	                    ->label('المستخدم')
   106	                    ->options(fn () => \App\Models\User::pluck('name', 'id')->toArray())
   107	                    ->searchable(),
   108	
   109	                Tables\Filters\Filter::make('date_range')
   110	                    ->label('فترة')
   111	                    ->form([
   112	                        Forms\Components\DatePicker::make('from')->label('من'),
   113	                        Forms\Components\DatePicker::make('to')->label('إلى'),
   114	                    ])
   115	                    ->query(function (Builder $query, array $data) {
   116	                        return $query
   117	                            ->when($data['from'], fn ($q, $d) => $q->whereDate('created_at', '>=', $d))
   118	                            ->when($data['to'], fn ($q, $d) => $q->whereDate('created_at', '<=', $d));
   119	                    }),
   120	            ])
   121	            ->actions([
   122	                Tables\Actions\ViewAction::make(),
   123	            ]);
   124	    }
   125	
   126	    public static function infolist(Infolist $infolist): Infolist
   127	    {
   128	        return $infolist->schema([
   129	            Infolists\Components\Section::make('تفاصيل النشاط')
   130	                ->schema([
   131	                    Infolists\Components\TextEntry::make('log_name')
   132	                        ->label('السجل')
   133	                        ->badge(),
   134	
   135	                    Infolists\Components\TextEntry::make('description')
   136	                        ->label('الوصف'),
   137	
   138	                    Infolists\Components\TextEntry::make('subject_type')
   139	                        ->label('نوع الكائن')
   140	                        ->formatStateUsing(fn (?string $state) => $state ? class_basename($state) : '—'),
   141	
   142	                    Infolists\Components\TextEntry::make('subject_id')
   143	                        ->label('معرف الكائن')
   144	                        ->placeholder('—'),
   145	
   146	                    Infolists\Components\TextEntry::make('causer.name')
   147	                        ->label('بواسطة')
   148	                        ->placeholder('النظام'),
   149	
   150	                    Infolists\Components\TextEntry::make('created_at')
   151	                        ->label('التاريخ')
   152	                        ->dateTime('d/m/Y H:i:s'),
   153	                ])->columns(2),
   154	
   155	            Infolists\Components\Section::make('التفاصيل التقنية')
   156	                ->schema([
   157	                    Infolists\Components\TextEntry::make('properties.old')
   158	                        ->label('القيم القديمة')
   159	                        ->formatStateUsing(function ($state) {
   160	                            if (empty($state)) return '—';
   161	                            $data = is_array($state) ? $state : json_decode($state, true);
   162	                            return collect($data)->map(fn ($v, $k) => "{$k}: " . (is_array($v) ? json_encode($v, JSON_UNESCAPED_UNICODE) : $v))->join("\n");
   163	                        })
   164	                        ->markdown()
   165	                        ->columnSpanFull(),
   166	
   167	                    Infolists\Components\TextEntry::make('properties.attributes')
   168	                        ->label('القيم الجديدة')
   169	                        ->formatStateUsing(function ($state) {
   170	                            if (empty($state)) return '—';
   171	                            $data = is_array($state) ? $state : json_decode($state, true);
   172	                            return collect($data)->map(fn ($v, $k) => "{$k}: " . (is_array($v) ? json_encode($v, JSON_UNESCAPED_UNICODE) : $v))->join("\n");
   173	                        })
   174	                        ->markdown()
   175	                        ->columnSpanFull(),
   176	                ]),
   177	        ]);
   178	    }
   179	
   180	    public static function getPages(): array
   181	    {
   182	        return [
   183	            'index' => Pages\ListActivityLogs::route('/'),
   184	            'view' => Pages\ViewActivityLog::route('/{record}'),
   185	        ];
   186	    }
   187	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [084]: app/Filament/Resources/ActivityLogResource.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [085/494]: app/Filament/Resources/ActivityLogResource/Pages/ListActivityLogs.php
│ LANGUAGE: php | LINES: 37 | SIZE: 1374 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Filament\Resources\ActivityLogResource\Pages;
     4	
     5	use App\Filament\Resources\ActivityLogResource;
     6	use App\Services\Admin\AuditService;
     7	use Filament\Actions;
     8	use Filament\Resources\Pages\ListRecords;
     9	use Filament\Notifications\Notification;
    10	
    11	class ListActivityLogs extends ListRecords
    12	{
    13	    protected static string $resource = ActivityLogResource::class;
    14	
    15	    protected function getHeaderActions(): array
    16	    {
    17	        return [
    18	            Actions\Action::make('purge')
    19	                ->label('تنظيف السجلات القديمة')
    20	                ->icon('heroicon-o-trash')
    21	                ->color('danger')
    22	                ->requiresConfirmation()
    23	                ->modalHeading('تنظيف السجلات القديمة')
    24	                ->form([
    25	                    \Filament\Forms\Components\TextInput::make('days')
    26	                        ->label('حذف السجلات الأقدم من (أيام)')
    27	                        ->numeric()
    28	                        ->default(365)
    29	                        ->required()
    30	                        ->minValue(30),
    31	                ])
    32	                ->action(function (array $data) {
    33	                    $count = app(AuditService::class)->purgeOlderThan($data['days']);
    34	                    Notification::make()->title("تم حذف {$count} سجل")->success()->send();
    35	                }),
    36	        ];
    37	    }
    38	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [085]: app/Filament/Resources/ActivityLogResource/Pages/ListActivityLogs.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [086/494]: app/Filament/Resources/ActivityLogResource/Pages/ViewActivityLog.php
│ LANGUAGE: php | LINES: 10 | SIZE: 270 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Filament\Resources\ActivityLogResource\Pages;
     4	
     5	use App\Filament\Resources\ActivityLogResource;
     6	use Filament\Resources\Pages\ViewRecord;
     7	
     8	class ViewActivityLog extends ViewRecord
     9	{
    10	    protected static string $resource = ActivityLogResource::class;
    11	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [086]: app/Filament/Resources/ActivityLogResource/Pages/ViewActivityLog.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [087/494]: app/Filament/Resources/BoardOfferResource.php
│ LANGUAGE: php | LINES: 455 | SIZE: 19976 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Filament\Resources;
     4	
     5	use App\Filament\Resources\BoardOfferResource\Pages;
     6	use App\Filament\Resources\BoardOfferResource\RelationManagers;
     7	use App\Models\BoardOffer;
     8	use App\Enums\BoardOfferStatus;
     9	use App\Enums\BoardDecisionType;
    10	use App\Enums\BoardDecisionResult;
    11	use App\Services\Admin\BoardService;
    12	use Filament\Forms;
    13	use Filament\Forms\Form;
    14	use Filament\Resources\Resource;
    15	use Filament\Tables;
    16	use Filament\Tables\Table;
    17	use Filament\Infolists;
    18	use Filament\Infolists\Infolist;
    19	use Filament\Notifications\Notification;
    20	use Illuminate\Database\Eloquent\Builder;
    21	
    22	class BoardOfferResource extends Resource
    23	{
    24	    protected static ?string $model = BoardOffer::class;
    25	
    26	    protected static ?string $navigationIcon = 'heroicon-o-clipboard-document-list';
    27	
    28	    protected static ?string $navigationGroup = 'شؤون المجلس';
    29	
    30	    protected static ?string $navigationLabel = 'عروض المجلس';
    31	
    32	    protected static ?string $modelLabel = 'عرض مجلس';
    33	
    34	    protected static ?string $pluralModelLabel = 'عروض المجلس';
    35	
    36	    protected static ?int $navigationSort = 1;
    37	
    38	    public static function form(Form $form): Form
    39	    {
    40	        return $form->schema([
    41	            Forms\Components\Section::make('بيانات العرض')
    42	                ->schema([
    43	                    Forms\Components\TextInput::make('offer_number')
    44	                        ->label('رقم العرض')
    45	                        ->disabled()
    46	                        ->dehydrated(false)
    47	                        ->visible(fn (string $operation) => $operation !== 'create'),
    48	
    49	                    Forms\Components\Select::make('member_id')
    50	                        ->label('العضو المعني')
    51	                        ->relationship('member', 'full_name_ar')
    52	                        ->searchable()
    53	                        ->preload()
    54	                        ->nullable(),
    55	
    56	                    Forms\Components\TextInput::make('subject')
    57	                        ->label('الموضوع')
    58	                        ->required()
    59	                        ->maxLength(500)
    60	                        ->columnSpanFull(),
    61	
    62	                    Forms\Components\RichEditor::make('description')
    63	                        ->label('التفاصيل')
    64	                        ->required()
    65	                        ->columnSpanFull(),
    66	
    67	                    Forms\Components\Select::make('category')
    68	                        ->label('التصنيف')
    69	                        ->options([
    70	                            'membership' => 'شؤون العضوية',
    71	                            'financial' => 'شؤون مالية',
    72	                            'disciplinary' => 'شؤون تأديبية',
    73	                            'administrative' => 'شؤون إدارية',
    74	                            'facilities' => 'المرافق والخدمات',
    75	                            'events' => 'الفعاليات',
    76	                            'other' => 'أخرى',
    77	                        ])
    78	                        ->required(),
    79	
    80	                    Forms\Components\Select::make('priority')
    81	                        ->label('الأولوية')
    82	                        ->options([
    83	                            'low' => 'منخفضة',
    84	                            'normal' => 'عادية',
    85	                            'high' => 'عالية',
    86	                            'urgent' => 'عاجلة',
    87	                        ])
    88	                        ->default('normal')
    89	                        ->required(),
    90	
    91	                    Forms\Components\DatePicker::make('meeting_date')
    92	                        ->label('تاريخ الاجتماع')
    93	                        ->nullable(),
    94	
    95	                    Forms\Components\TextInput::make('agenda_order')
    96	                        ->label('ترتيب جدول الأعمال')
    97	                        ->numeric()
    98	                        ->nullable(),
    99	                ])->columns(2),
   100	
   101	            Forms\Components\Section::make('ملاحظات')
   102	                ->schema([
   103	                    Forms\Components\Textarea::make('notes')
   104	                        ->label('ملاحظات إضافية')
   105	                        ->rows(3)
   106	                        ->columnSpanFull(),
   107	                ]),
   108	
   109	            Forms\Components\Section::make('المرفقات')
   110	                ->schema([
   111	                    Forms\Components\SpatieMediaLibraryFileUpload::make('attachments')
   112	                        ->label('المرفقات')
   113	                        ->collection('attachments')
   114	                        ->multiple()
   115	                        ->acceptedFileTypes(['application/pdf', 'image/*', 'application/msword', 'application/vnd.openxmlformats-officedocument.wordprocessingml.document'])
   116	                        ->maxSize(10240)
   117	                        ->columnSpanFull(),
   118	                ]),
   119	        ]);
   120	    }
   121	
   122	    public static function table(Table $table): Table
   123	    {
   124	        return $table
   125	            ->columns([
   126	                Tables\Columns\TextColumn::make('offer_number')
   127	                    ->label('رقم العرض')
   128	                    ->searchable()
   129	                    ->sortable()
   130	                    ->copyable(),
   131	
   132	                Tables\Columns\TextColumn::make('subject')
   133	                    ->label('الموضوع')
   134	                    ->searchable()
   135	                    ->wrap()
   136	                    ->limit(60),
   137	
   138	                Tables\Columns\TextColumn::make('member.full_name_ar')
   139	                    ->label('العضو')
   140	                    ->searchable()
   141	                    ->placeholder('—'),
   142	
   143	                Tables\Columns\TextColumn::make('category')
   144	                    ->label('التصنيف')
   145	                    ->badge()
   146	                    ->formatStateUsing(fn (string $state) => match ($state) {
   147	                        'membership' => 'شؤون العضوية',
   148	                        'financial' => 'شؤون مالية',
   149	                        'disciplinary' => 'شؤون تأديبية',
   150	                        'administrative' => 'شؤون إدارية',
   151	                        'facilities' => 'المرافق والخدمات',
   152	                        'events' => 'الفعاليات',
   153	                        default => 'أخرى',
   154	                    }),
   155	
   156	                Tables\Columns\TextColumn::make('priority')
   157	                    ->label('الأولوية')
   158	                    ->badge()
   159	                    ->color(fn (string $state) => match ($state) {
   160	                        'low' => 'gray',
   161	                        'normal' => 'info',
   162	                        'high' => 'warning',
   163	                        'urgent' => 'danger',
   164	                        default => 'gray',
   165	                    })
   166	                    ->formatStateUsing(fn (string $state) => match ($state) {
   167	                        'low' => 'منخفضة',
   168	                        'normal' => 'عادية',
   169	                        'high' => 'عالية',
   170	                        'urgent' => 'عاجلة',
   171	                        default => $state,
   172	                    }),
   173	
   174	                Tables\Columns\TextColumn::make('status')
   175	                    ->label('الحالة')
   176	                    ->badge()
   177	                    ->color(fn (BoardOfferStatus $state) => $state->getColor())
   178	                    ->formatStateUsing(fn (BoardOfferStatus $state) => $state->getLabel()),
   179	
   180	                Tables\Columns\TextColumn::make('submittedBy.name')
   181	                    ->label('مقدم من')
   182	                    ->toggleable(),
   183	
   184	                Tables\Columns\TextColumn::make('meeting_date')
   185	                    ->label('تاريخ الاجتماع')
   186	                    ->date('d/m/Y')
   187	                    ->sortable()
   188	                    ->toggleable(),
   189	
   190	                Tables\Columns\TextColumn::make('submitted_at')
   191	                    ->label('تاريخ التقديم')
   192	                    ->dateTime('d/m/Y H:i')
   193	                    ->sortable(),
   194	            ])
   195	            ->defaultSort('submitted_at', 'desc')
   196	            ->filters([
   197	                Tables\Filters\SelectFilter::make('status')
   198	                    ->label('الحالة')
   199	                    ->options(BoardOfferStatus::toFilamentOptions()),
   200	
   201	                Tables\Filters\SelectFilter::make('category')
   202	                    ->label('التصنيف')
   203	                    ->options([
   204	                        'membership' => 'شؤون العضوية',
   205	                        'financial' => 'شؤون مالية',
   206	                        'disciplinary' => 'شؤون تأديبية',
   207	                        'administrative' => 'شؤون إدارية',
   208	                        'facilities' => 'المرافق والخدمات',
   209	                        'events' => 'الفعاليات',
   210	                        'other' => 'أخرى',
   211	                    ]),
   212	
   213	                Tables\Filters\SelectFilter::make('priority')
   214	                    ->label('الأولوية')
   215	                    ->options([
   216	                        'low' => 'منخفضة',
   217	                        'normal' => 'عادية',
   218	                        'high' => 'عالية',
   219	                        'urgent' => 'عاجلة',
   220	                    ]),
   221	
   222	                Tables\Filters\Filter::make('meeting_date')
   223	                    ->label('تاريخ الاجتماع')
   224	                    ->form([
   225	                        Forms\Components\DatePicker::make('meeting_from')->label('من'),
   226	                        Forms\Components\DatePicker::make('meeting_to')->label('إلى'),
   227	                    ])
   228	                    ->query(function (Builder $query, array $data): Builder {
   229	                        return $query
   230	                            ->when($data['meeting_from'], fn (Builder $q, $date) => $q->whereDate('meeting_date', '>=', $date))
   231	                            ->when($data['meeting_to'], fn (Builder $q, $date) => $q->whereDate('meeting_date', '<=', $date));
   232	                    }),
   233	            ])
   234	            ->actions([
   235	                Tables\Actions\ViewAction::make(),
   236	                Tables\Actions\EditAction::make()
   237	                    ->visible(fn (BoardOffer $record) => !$record->isFinalized()),
   238	
   239	                Tables\Actions\Action::make('submitForReview')
   240	                    ->label('تقديم للمراجعة')
   241	                    ->icon('heroicon-o-paper-airplane')
   242	                    ->color('info')
   243	                    ->requiresConfirmation()
   244	                    ->modalHeading('تقديم العرض للمراجعة')
   245	                    ->modalDescription('هل أنت متأكد من تقديم هذا العرض لمراجعة المجلس؟')
   246	                    ->action(function (BoardOffer $record) {
   247	                        try {
   248	                            app(BoardService::class)->submitForReview($record);
   249	                            Notification::make()->title('تم تقديم العرض للمراجعة')->success()->send();
   250	                        } catch (\LogicException $e) {
   251	                            Notification::make()->title($e->getMessage())->danger()->send();
   252	                        }
   253	                    })
   254	                    ->visible(fn (BoardOffer $record) => $record->isPending()),
   255	
   256	                Tables\Actions\Action::make('recordDecision')
   257	                    ->label('تسجيل قرار')
   258	                    ->icon('heroicon-o-scale')
   259	                    ->color('warning')
   260	                    ->form([
   261	                        Forms\Components\Select::make('decision_type')
   262	                            ->label('نوع القرار')
   263	                            ->options(BoardDecisionType::toFilamentOptions())
   264	                            ->required(),
   265	
   266	                        Forms\Components\Select::make('result')
   267	                            ->label('النتيجة')
   268	                            ->options(BoardDecisionResult::toFilamentOptions())
   269	                            ->required(),
   270	
   271	                        Forms\Components\DatePicker::make('decision_date')
   272	                            ->label('تاريخ القرار')
   273	                            ->default(now())
   274	                            ->required(),
   275	
   276	                        Forms\Components\Grid::make(3)->schema([
   277	                            Forms\Components\TextInput::make('voting_for')
   278	                                ->label('موافق')
   279	                                ->numeric()
   280	                                ->minValue(0),
   281	
   282	                            Forms\Components\TextInput::make('voting_against')
   283	                                ->label('معارض')
   284	                                ->numeric()
   285	                                ->minValue(0),
   286	
   287	                            Forms\Components\TextInput::make('voting_abstain')
   288	                                ->label('ممتنع')
   289	                                ->numeric()
   290	                                ->minValue(0),
   291	                        ]),
   292	
   293	                        Forms\Components\Textarea::make('conditions')
   294	                            ->label('الشروط')
   295	                            ->rows(2),
   296	
   297	                        Forms\Components\DatePicker::make('effective_date')
   298	                            ->label('تاريخ السريان'),
   299	
   300	                        Forms\Components\DatePicker::make('expiry_date')
   301	                            ->label('تاريخ الانتهاء'),
   302	
   303	                        Forms\Components\Textarea::make('notes')
   304	                            ->label('ملاحظات')
   305	                            ->rows(3),
   306	                    ])
   307	                    ->action(function (BoardOffer $record, array $data) {
   308	                        try {
   309	                            $data['result'] = BoardDecisionResult::from($data['result']);
   310	                            $data['decision_type'] = BoardDecisionType::from($data['decision_type']);
   311	                            app(BoardService::class)->recordDecision($record, $data);
   312	                            Notification::make()->title('تم تسجيل القرار بنجاح')->success()->send();
   313	                        } catch (\Throwable $e) {
   314	                            Notification::make()->title($e->getMessage())->danger()->send();
   315	                        }
   316	                    })
   317	                    ->visible(fn (BoardOffer $record) => $record->status === BoardOfferStatus::UNDER_REVIEW),
   318	
   319	                Tables\Actions\Action::make('cancel')
   320	                    ->label('إلغاء')
   321	                    ->icon('heroicon-o-x-mark')
   322	                    ->color('danger')
   323	                    ->requiresConfirmation()
   324	                    ->form([
   325	                        Forms\Components\Textarea::make('reason')
   326	                            ->label('سبب الإلغاء')
   327	                            ->required(),
   328	                    ])
   329	                    ->action(function (BoardOffer $record, array $data) {
   330	                        try {
   331	                            app(BoardService::class)->cancelOffer($record, $data['reason']);
   332	                            Notification::make()->title('تم إلغاء العرض')->success()->send();
   333	                        } catch (\LogicException $e) {
   334	                            Notification::make()->title($e->getMessage())->danger()->send();
   335	                        }
   336	                    })
   337	                    ->visible(fn (BoardOffer $record) => !$record->isFinalized()),
   338	            ])
   339	            ->bulkActions([
   340	                Tables\Actions\BulkActionGroup::make([
   341	                    Tables\Actions\DeleteBulkAction::make(),
   342	                ]),
   343	            ]);
   344	    }
   345	
   346	    public static function infolist(Infolist $infolist): Infolist
   347	    {
   348	        return $infolist->schema([
   349	            Infolists\Components\Section::make('بيانات العرض')
   350	                ->schema([
   351	                    Infolists\Components\TextEntry::make('offer_number')
   352	                        ->label('رقم العرض')
   353	                        ->copyable(),
   354	
   355	                    Infolists\Components\TextEntry::make('status')
   356	                        ->label('الحالة')
   357	                        ->badge()
   358	                        ->color(fn (BoardOfferStatus $state) => $state->getColor())
   359	                        ->formatStateUsing(fn (BoardOfferStatus $state) => $state->getLabel()),
   360	
   361	                    Infolists\Components\TextEntry::make('subject')
   362	                        ->label('الموضوع')
   363	                        ->columnSpanFull(),
   364	
   365	                    Infolists\Components\TextEntry::make('description')
   366	                        ->label('التفاصيل')
   367	                        ->html()
   368	                        ->columnSpanFull(),
   369	
   370	                    Infolists\Components\TextEntry::make('member.full_name_ar')
   371	                        ->label('العضو المعني')
   372	                        ->placeholder('—'),
   373	
   374	                    Infolists\Components\TextEntry::make('category')
   375	                        ->label('التصنيف')
   376	                        ->badge(),
   377	
   378	                    Infolists\Components\TextEntry::make('priority')
   379	                        ->label('الأولوية')
   380	                        ->badge()
   381	                        ->color(fn (string $state) => match ($state) {
   382	                            'urgent' => 'danger',
   383	                            'high' => 'warning',
   384	                            'normal' => 'info',
   385	                            default => 'gray',
   386	                        }),
   387	
   388	                    Infolists\Components\TextEntry::make('meeting_date')
   389	                        ->label('تاريخ الاجتماع')
   390	                        ->date('d/m/Y'),
   391	
   392	                    Infolists\Components\TextEntry::make('agenda_order')
   393	                        ->label('ترتيب جدول الأعمال'),
   394	
   395	                    Infolists\Components\TextEntry::make('submittedBy.name')
   396	                        ->label('مقدم من'),
   397	
   398	                    Infolists\Components\TextEntry::make('submitted_at')
   399	                        ->label('تاريخ التقديم')
   400	                        ->dateTime('d/m/Y H:i'),
   401	
   402	                    Infolists\Components\TextEntry::make('reviewedBy.name')
   403	                        ->label('تمت المراجعة بواسطة')
   404	                        ->placeholder('—'),
   405	
   406	                    Infolists\Components\TextEntry::make('reviewed_at')
   407	                        ->label('تاريخ المراجعة')
   408	                        ->dateTime('d/m/Y H:i')
   409	                        ->placeholder('—'),
   410	                ])->columns(2),
   411	
   412	            Infolists\Components\Section::make('ملاحظات')
   413	                ->schema([
   414	                    Infolists\Components\TextEntry::make('notes')
   415	                        ->label('ملاحظات')
   416	                        ->placeholder('لا توجد ملاحظات')
   417	                        ->columnSpanFull(),
   418	
   419	                    Infolists\Components\TextEntry::make('cancellation_reason')
   420	                        ->label('سبب الإلغاء')
   421	                        ->visible(fn (BoardOffer $record) => $record->status === BoardOfferStatus::CANCELLED)
   422	                        ->columnSpanFull(),
   423	                ]),
   424	        ]);
   425	    }
   426	
   427	    public static function getRelations(): array
   428	    {
   429	        return [
   430	            RelationManagers\DecisionsRelationManager::class,
   431	        ];
   432	    }
   433	
   434	    public static function getPages(): array
   435	    {
   436	        return [
   437	            'index' => Pages\ListBoardOffers::route('/'),
   438	            'create' => Pages\CreateBoardOffer::route('/create'),
   439	            'view' => Pages\ViewBoardOffer::route('/{record}'),
   440	            'edit' => Pages\EditBoardOffer::route('/{record}/edit'),
   441	        ];
   442	    }
   443	
   444	    public static function getNavigationBadge(): ?string
   445	    {
   446	        return static::getModel()::whereIn('status', [
   447	            BoardOfferStatus::PENDING,
   448	            BoardOfferStatus::UNDER_REVIEW,
   449	        ])->count() ?: null;
   450	    }
   451	
   452	    public static function getNavigationBadgeColor(): string|array|null
   453	    {
   454	        return 'warning';
   455	    }
   456	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [087]: app/Filament/Resources/BoardOfferResource.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [088/494]: app/Filament/Resources/BoardOfferResource/Pages/CreateBoardOffer.php
│ LANGUAGE: php | LINES: 27 | SIZE: 842 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Filament\Resources\BoardOfferResource\Pages;
     4	
     5	use App\Filament\Resources\BoardOfferResource;
     6	use App\Services\Admin\BoardService;
     7	use Filament\Resources\Pages\CreateRecord;
     8	
     9	class CreateBoardOffer extends CreateRecord
    10	{
    11	    protected static string $resource = BoardOfferResource::class;
    12	
    13	    protected function mutateFormDataBeforeCreate(array $data): array
    14	    {
    15	        $sequenceService = app(\App\Services\Admin\SequenceService::class);
    16	        $data['offer_number'] = $sequenceService->getNextNumber('board_offer_number');
    17	        $data['status'] = \App\Enums\BoardOfferStatus::PENDING;
    18	        $data['submitted_by'] = auth()->id();
    19	        $data['submitted_at'] = now();
    20	
    21	        return $data;
    22	    }
    23	
    24	    protected function getRedirectUrl(): string
    25	    {
    26	        return $this->getResource()::getUrl('index');
    27	    }
    28	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [088]: app/Filament/Resources/BoardOfferResource/Pages/CreateBoardOffer.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [089/494]: app/Filament/Resources/BoardOfferResource/Pages/EditBoardOffer.php
│ LANGUAGE: php | LINES: 25 | SIZE: 669 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Filament\Resources\BoardOfferResource\Pages;
     4	
     5	use App\Filament\Resources\BoardOfferResource;
     6	use Filament\Actions;
     7	use Filament\Resources\Pages\EditRecord;
     8	
     9	class EditBoardOffer extends EditRecord
    10	{
    11	    protected static string $resource = BoardOfferResource::class;
    12	
    13	    protected function getHeaderActions(): array
    14	    {
    15	        return [
    16	            Actions\ViewAction::make(),
    17	            Actions\DeleteAction::make()
    18	                ->visible(fn () => !$this->record->isFinalized()),
    19	        ];
    20	    }
    21	
    22	    protected function getRedirectUrl(): string
    23	    {
    24	        return $this->getResource()::getUrl('view', ['record' => $this->record]);
    25	    }
    26	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [089]: app/Filament/Resources/BoardOfferResource/Pages/EditBoardOffer.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [090/494]: app/Filament/Resources/BoardOfferResource/Pages/ListBoardOffers.php
│ LANGUAGE: php | LINES: 46 | SIZE: 1734 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Filament\Resources\BoardOfferResource\Pages;
     4	
     5	use App\Filament\Resources\BoardOfferResource;
     6	use Filament\Actions;
     7	use Filament\Resources\Pages\ListRecords;
     8	use Filament\Resources\Components\Tab;
     9	use App\Enums\BoardOfferStatus;
    10	
    11	class ListBoardOffers extends ListRecords
    12	{
    13	    protected static string $resource = BoardOfferResource::class;
    14	
    15	    protected function getHeaderActions(): array
    16	    {
    17	        return [
    18	            Actions\CreateAction::make(),
    19	        ];
    20	    }
    21	
    22	    public function getTabs(): array
    23	    {
    24	        return [
    25	            'all' => Tab::make('الكل')
    26	                ->icon('heroicon-o-clipboard-document-list'),
    27	
    28	            'pending' => Tab::make('معلقة')
    29	                ->icon('heroicon-o-clock')
    30	                ->modifyQueryUsing(fn ($query) => $query->where('status', BoardOfferStatus::PENDING))
    31	                ->badge(fn () => \App\Models\BoardOffer::where('status', BoardOfferStatus::PENDING)->count()),
    32	
    33	            'under_review' => Tab::make('قيد المراجعة')
    34	                ->icon('heroicon-o-eye')
    35	                ->modifyQueryUsing(fn ($query) => $query->where('status', BoardOfferStatus::UNDER_REVIEW))
    36	                ->badge(fn () => \App\Models\BoardOffer::where('status', BoardOfferStatus::UNDER_REVIEW)->count()),
    37	
    38	            'approved' => Tab::make('موافق عليها')
    39	                ->icon('heroicon-o-check-circle')
    40	                ->modifyQueryUsing(fn ($query) => $query->where('status', BoardOfferStatus::APPROVED)),
    41	
    42	            'rejected' => Tab::make('مرفوضة')
    43	                ->icon('heroicon-o-x-circle')
    44	                ->modifyQueryUsing(fn ($query) => $query->where('status', BoardOfferStatus::REJECTED)),
    45	        ];
    46	    }
    47	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [090]: app/Filament/Resources/BoardOfferResource/Pages/ListBoardOffers.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [091/494]: app/Filament/Resources/BoardOfferResource/Pages/ViewBoardOffer.php
│ LANGUAGE: php | LINES: 19 | SIZE: 484 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Filament\Resources\BoardOfferResource\Pages;
     4	
     5	use App\Filament\Resources\BoardOfferResource;
     6	use Filament\Actions;
     7	use Filament\Resources\Pages\ViewRecord;
     8	
     9	class ViewBoardOffer extends ViewRecord
    10	{
    11	    protected static string $resource = BoardOfferResource::class;
    12	
    13	    protected function getHeaderActions(): array
    14	    {
    15	        return [
    16	            Actions\EditAction::make()
    17	                ->visible(fn () => !$this->record->isFinalized()),
    18	        ];
    19	    }
    20	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [091]: app/Filament/Resources/BoardOfferResource/Pages/ViewBoardOffer.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [092/494]: app/Filament/Resources/BoardOfferResource/RelationManagers/DecisionsRelationManager.php
│ LANGUAGE: php | LINES: 130 | SIZE: 4596 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Filament\Resources\BoardOfferResource\RelationManagers;
     4	
     5	use App\Enums\BoardDecisionType;
     6	use App\Enums\BoardDecisionResult;
     7	use Filament\Forms;
     8	use Filament\Forms\Form;
     9	use Filament\Resources\RelationManagers\RelationManager;
    10	use Filament\Tables;
    11	use Filament\Tables\Table;
    12	
    13	class DecisionsRelationManager extends RelationManager
    14	{
    15	    protected static string $relationship = 'decisions';
    16	
    17	    protected static ?string $title = 'القرارات';
    18	
    19	    protected static ?string $modelLabel = 'قرار';
    20	
    21	    public function form(Form $form): Form
    22	    {
    23	        return $form->schema([
    24	            Forms\Components\Select::make('decision_type')
    25	                ->label('نوع القرار')
    26	                ->options(BoardDecisionType::toFilamentOptions())
    27	                ->required(),
    28	
    29	            Forms\Components\Select::make('result')
    30	                ->label('النتيجة')
    31	                ->options(BoardDecisionResult::toFilamentOptions())
    32	                ->required(),
    33	
    34	            Forms\Components\DatePicker::make('decision_date')
    35	                ->label('تاريخ القرار')
    36	                ->default(now())
    37	                ->required(),
    38	
    39	            Forms\Components\Grid::make(3)->schema([
    40	                Forms\Components\TextInput::make('voting_for')
    41	                    ->label('موافق')
    42	                    ->numeric()
    43	                    ->minValue(0)
    44	                    ->default(0),
    45	
    46	                Forms\Components\TextInput::make('voting_against')
    47	                    ->label('معارض')
    48	                    ->numeric()
    49	                    ->minValue(0)
    50	                    ->default(0),
    51	
    52	                Forms\Components\TextInput::make('voting_abstain')
    53	                    ->label('ممتنع')
    54	                    ->numeric()
    55	                    ->minValue(0)
    56	                    ->default(0),
    57	            ]),
    58	
    59	            Forms\Components\Textarea::make('conditions')
    60	                ->label('الشروط')
    61	                ->rows(2)
    62	                ->columnSpanFull(),
    63	
    64	            Forms\Components\DatePicker::make('effective_date')
    65	                ->label('تاريخ السريان'),
    66	
    67	            Forms\Components\DatePicker::make('expiry_date')
    68	                ->label('تاريخ الانتهاء'),
    69	
    70	            Forms\Components\Textarea::make('notes')
    71	                ->label('ملاحظات')
    72	                ->rows(3)
    73	                ->columnSpanFull(),
    74	        ]);
    75	    }
    76	
    77	    public function table(Table $table): Table
    78	    {
    79	        return $table
    80	            ->columns([
    81	                Tables\Columns\TextColumn::make('decision_type')
    82	                    ->label('نوع القرار')
    83	                    ->badge()
    84	                    ->formatStateUsing(fn (BoardDecisionType $state) => $state->getLabel())
    85	                    ->color(fn (BoardDecisionType $state) => $state->getColor()),
    86	
    87	                Tables\Columns\TextColumn::make('result')
    88	                    ->label('النتيجة')
    89	                    ->badge()
    90	                    ->formatStateUsing(fn (BoardDecisionResult $state) => $state->getLabel())
    91	                    ->color(fn (BoardDecisionResult $state) => $state->getColor()),
    92	
    93	                Tables\Columns\TextColumn::make('decision_date')
    94	                    ->label('التاريخ')
    95	                    ->date('d/m/Y')
    96	                    ->sortable(),
    97	
    98	                Tables\Columns\TextColumn::make('voting_for')
    99	                    ->label('موافق')
   100	                    ->alignCenter(),
   101	
   102	                Tables\Columns\TextColumn::make('voting_against')
   103	                    ->label('معارض')
   104	                    ->alignCenter(),
   105	
   106	                Tables\Columns\TextColumn::make('voting_abstain')
   107	                    ->label('ممتنع')
   108	                    ->alignCenter(),
   109	
   110	                Tables\Columns\TextColumn::make('decidedBy.name')
   111	                    ->label('بواسطة'),
   112	
   113	                Tables\Columns\TextColumn::make('effective_date')
   114	                    ->label('السريان')
   115	                    ->date('d/m/Y')
   116	                    ->toggleable(),
   117	            ])
   118	            ->defaultSort('decision_date', 'desc')
   119	            ->headerActions([
   120	                Tables\Actions\CreateAction::make()
   121	                    ->mutateFormDataUsing(function (array $data) {
   122	                        $data['decided_by'] = auth()->id();
   123	                        return $data;
   124	                    }),
   125	            ])
   126	            ->actions([
   127	                Tables\Actions\ViewAction::make(),
   128	                Tables\Actions\EditAction::make(),
   129	            ]);
   130	    }
   131	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [092]: app/Filament/Resources/BoardOfferResource/RelationManagers/DecisionsRelationManager.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [093/494]: app/Filament/Resources/CashRegisterResource.php
│ LANGUAGE: php | LINES: 345 | SIZE: 14673 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Filament\Resources;
     4	
     5	use App\Filament\Resources\CashRegisterResource\Pages;
     6	use App\Filament\Resources\CashRegisterResource\RelationManagers;
     7	use App\Models\CashRegister;
     8	use App\Enums\CashRegisterStatus;
     9	use App\Services\Admin\CashRegisterService;
    10	use Filament\Forms;
    11	use Filament\Forms\Form;
    12	use Filament\Resources\Resource;
    13	use Filament\Tables;
    14	use Filament\Tables\Table;
    15	use Filament\Infolists;
    16	use Filament\Infolists\Infolist;
    17	use Filament\Notifications\Notification;
    18	
    19	class CashRegisterResource extends Resource
    20	{
    21	    protected static ?string $model = CashRegister::class;
    22	
    23	    protected static ?string $navigationIcon = 'heroicon-o-banknotes';
    24	
    25	    protected static ?string $navigationGroup = 'إدارة النظام';
    26	
    27	    protected static ?string $navigationLabel = 'السجلات النقدية';
    28	
    29	    protected static ?string $modelLabel = 'سجل نقدي';
    30	
    31	    protected static ?string $pluralModelLabel = 'السجلات النقدية';
    32	
    33	    protected static ?int $navigationSort = 5;
    34	
    35	    public static function form(Form $form): Form
    36	    {
    37	        return $form->schema([
    38	            Forms\Components\Section::make('بيانات السجل')
    39	                ->schema([
    40	                    Forms\Components\TextInput::make('name')
    41	                        ->label('اسم السجل')
    42	                        ->required()
    43	                        ->maxLength(255),
    44	
    45	                    Forms\Components\TextInput::make('opening_balance')
    46	                        ->label('الرصيد الافتتاحي')
    47	                        ->numeric()
    48	                        ->prefix('ج.م')
    49	                        ->default(0)
    50	                        ->required()
    51	                        ->minValue(0),
    52	
    53	                    Forms\Components\Textarea::make('notes')
    54	                        ->label('ملاحظات')
    55	                        ->rows(3)
    56	                        ->columnSpanFull(),
    57	                ])->columns(2),
    58	        ]);
    59	    }
    60	
    61	    public static function table(Table $table): Table
    62	    {
    63	        return $table
    64	            ->columns([
    65	                Tables\Columns\TextColumn::make('name')
    66	                    ->label('اسم السجل')
    67	                    ->searchable()
    68	                    ->sortable(),
    69	
    70	                Tables\Columns\TextColumn::make('status')
    71	                    ->label('الحالة')
    72	                    ->badge()
    73	                    ->color(fn (CashRegisterStatus $state) => $state->getColor())
    74	                    ->formatStateUsing(fn (CashRegisterStatus $state) => $state->getLabel()),
    75	
    76	                Tables\Columns\TextColumn::make('opening_balance')
    77	                    ->label('الرصيد الافتتاحي')
    78	                    ->money('EGP')
    79	                    ->sortable(),
    80	
    81	                Tables\Columns\TextColumn::make('current_balance')
    82	                    ->label('الرصيد الحالي')
    83	                    ->money('EGP')
    84	                    ->sortable()
    85	                    ->color(fn ($state) => $state < 0 ? 'danger' : 'success'),
    86	
    87	                Tables\Columns\TextColumn::make('openedBy.name')
    88	                    ->label('فتح بواسطة'),
    89	
    90	                Tables\Columns\TextColumn::make('opened_at')
    91	                    ->label('تاريخ الفتح')
    92	                    ->dateTime('d/m/Y H:i')
    93	                    ->sortable(),
    94	
    95	                Tables\Columns\TextColumn::make('closedBy.name')
    96	                    ->label('أغلق بواسطة')
    97	                    ->placeholder('—'),
    98	
    99	                Tables\Columns\TextColumn::make('closed_at')
   100	                    ->label('تاريخ الإغلاق')
   101	                    ->dateTime('d/m/Y H:i')
   102	                    ->placeholder('—')
   103	                    ->toggleable(),
   104	
   105	                Tables\Columns\TextColumn::make('difference')
   106	                    ->label('الفرق')
   107	                    ->money('EGP')
   108	                    ->color(fn ($state) => $state == 0 ? 'success' : ($state > 0 ? 'warning' : 'danger'))
   109	                    ->placeholder('—')
   110	                    ->toggleable(),
   111	
   112	                Tables\Columns\TextColumn::make('transactions_count')
   113	                    ->label('عدد المعاملات')
   114	                    ->counts('transactions')
   115	                    ->sortable(),
   116	            ])
   117	            ->defaultSort('opened_at', 'desc')
   118	            ->filters([
   119	                Tables\Filters\SelectFilter::make('status')
   120	                    ->label('الحالة')
   121	                    ->options(CashRegisterStatus::toFilamentOptions()),
   122	
   123	                Tables\Filters\SelectFilter::make('opened_by')
   124	                    ->label('فتح بواسطة')
   125	                    ->relationship('openedBy', 'name'),
   126	
   127	                Tables\Filters\Filter::make('date_range')
   128	                    ->label('فترة')
   129	                    ->form([
   130	                        Forms\Components\DatePicker::make('from')->label('من'),
   131	                        Forms\Components\DatePicker::make('to')->label('إلى'),
   132	                    ])
   133	                    ->query(function ($query, array $data) {
   134	                        return $query
   135	                            ->when($data['from'], fn ($q, $d) => $q->whereDate('opened_at', '>=', $d))
   136	                            ->when($data['to'], fn ($q, $d) => $q->whereDate('opened_at', '<=', $d));
   137	                    }),
   138	            ])
   139	            ->actions([
   140	                Tables\Actions\ViewAction::make(),
   141	
   142	                Tables\Actions\Action::make('addTransaction')
   143	                    ->label('إضافة معاملة')
   144	                    ->icon('heroicon-o-plus-circle')
   145	                    ->color('info')
   146	                    ->form([
   147	                        Forms\Components\Select::make('type')
   148	                            ->label('النوع')
   149	                            ->options([
   150	                                'in' => 'وارد (إيداع)',
   151	                                'out' => 'صادر (سحب)',
   152	                            ])
   153	                            ->required(),
   154	
   155	                        Forms\Components\TextInput::make('amount')
   156	                            ->label('المبلغ')
   157	                            ->numeric()
   158	                            ->prefix('ج.م')
   159	                            ->required()
   160	                            ->minValue(0.01),
   161	
   162	                        Forms\Components\TextInput::make('description')
   163	                            ->label('الوصف')
   164	                            ->required()
   165	                            ->maxLength(500),
   166	                    ])
   167	                    ->action(function (CashRegister $record, array $data) {
   168	                        try {
   169	                            app(CashRegisterService::class)->addTransaction(
   170	                                $record,
   171	                                $data['type'],
   172	                                $data['amount'],
   173	                                $data['description']
   174	                            );
   175	                            Notification::make()->title('تمت إضافة المعاملة بنجاح')->success()->send();
   176	                        } catch (\LogicException $e) {
   177	                            Notification::make()->title($e->getMessage())->danger()->send();
   178	                        }
   179	                    })
   180	                    ->visible(fn (CashRegister $record) => $record->isOpen()),
   181	
   182	                Tables\Actions\Action::make('close')
   183	                    ->label('إغلاق السجل')
   184	                    ->icon('heroicon-o-lock-closed')
   185	                    ->color('danger')
   186	                    ->requiresConfirmation()
   187	                    ->form([
   188	                        Forms\Components\TextInput::make('counted_balance')
   189	                            ->label('الرصيد المحسوب يدوياً')
   190	                            ->numeric()
   191	                            ->prefix('ج.م')
   192	                            ->required(),
   193	
   194	                        Forms\Components\Textarea::make('notes')
   195	                            ->label('ملاحظات الإغلاق')
   196	                            ->rows(2),
   197	                    ])
   198	                    ->action(function (CashRegister $record, array $data) {
   199	                        try {
   200	                            app(CashRegisterService::class)->closeRegister(
   201	                                $record,
   202	                                $data['counted_balance'],
   203	                                $data['notes'] ?? null
   204	                            );
   205	                            Notification::make()->title('تم إغلاق السجل النقدي')->success()->send();
   206	                        } catch (\LogicException $e) {
   207	                            Notification::make()->title($e->getMessage())->danger()->send();
   208	                        }
   209	                    })
   210	                    ->visible(fn (CashRegister $record) => $record->isOpen()),
   211	
   212	                Tables\Actions\Action::make('suspend')
   213	                    ->label('تعليق')
   214	                    ->icon('heroicon-o-pause-circle')
   215	                    ->color('warning')
   216	                    ->requiresConfirmation()
   217	                    ->form([
   218	                        Forms\Components\Textarea::make('reason')
   219	                            ->label('سبب التعليق')
   220	                            ->required(),
   221	                    ])
   222	                    ->action(function (CashRegister $record, array $data) {
   223	                        try {
   224	                            app(CashRegisterService::class)->suspendRegister($record, $data['reason']);
   225	                            Notification::make()->title('تم تعليق السجل')->success()->send();
   226	                        } catch (\LogicException $e) {
   227	                            Notification::make()->title($e->getMessage())->danger()->send();
   228	                        }
   229	                    })
   230	                    ->visible(fn (CashRegister $record) => $record->isOpen()),
   231	
   232	                Tables\Actions\Action::make('resume')
   233	                    ->label('استئناف')
   234	                    ->icon('heroicon-o-play-circle')
   235	                    ->color('success')
   236	                    ->requiresConfirmation()
   237	                    ->action(function (CashRegister $record) {
   238	                        try {
   239	                            app(CashRegisterService::class)->resumeRegister($record);
   240	                            Notification::make()->title('تم استئناف السجل')->success()->send();
   241	                        } catch (\LogicException $e) {
   242	                            Notification::make()->title($e->getMessage())->danger()->send();
   243	                        }
   244	                    })
   245	                    ->visible(fn (CashRegister $record) => $record->status === CashRegisterStatus::SUSPENDED),
   246	            ])
   247	            ->bulkActions([]);
   248	    }
   249	
   250	    public static function infolist(Infolist $infolist): Infolist
   251	    {
   252	        return $infolist->schema([
   253	            Infolists\Components\Section::make('بيانات السجل')
   254	                ->schema([
   255	                    Infolists\Components\TextEntry::make('name')
   256	                        ->label('اسم السجل'),
   257	
   258	                    Infolists\Components\TextEntry::make('status')
   259	                        ->label('الحالة')
   260	                        ->badge()
   261	                        ->color(fn (CashRegisterStatus $state) => $state->getColor())
   262	                        ->formatStateUsing(fn (CashRegisterStatus $state) => $state->getLabel()),
   263	
   264	                    Infolists\Components\TextEntry::make('opening_balance')
   265	                        ->label('الرصيد الافتتاحي')
   266	                        ->money('EGP'),
   267	
   268	                    Infolists\Components\TextEntry::make('current_balance')
   269	                        ->label('الرصيد الحالي')
   270	                        ->money('EGP')
   271	                        ->color('success'),
   272	
   273	                    Infolists\Components\TextEntry::make('closing_balance')
   274	                        ->label('رصيد الإغلاق')
   275	                        ->money('EGP')
   276	                        ->placeholder('—'),
   277	
   278	                    Infolists\Components\TextEntry::make('counted_balance')
   279	                        ->label('الرصيد المحسوب')
   280	                        ->money('EGP')
   281	                        ->placeholder('—'),
   282	
   283	                    Infolists\Components\TextEntry::make('difference')
   284	                        ->label('الفرق')
   285	                        ->money('EGP')
   286	                        ->color(fn ($state) => $state == 0 ? 'success' : 'danger')
   287	                        ->placeholder('—'),
   288	                ])->columns(3),
   289	
   290	            Infolists\Components\Section::make('معلومات الفتح والإغلاق')
   291	                ->schema([
   292	                    Infolists\Components\TextEntry::make('openedBy.name')
   293	                        ->label('فتح بواسطة'),
   294	
   295	                    Infolists\Components\TextEntry::make('opened_at')
   296	                        ->label('تاريخ الفتح')
   297	                        ->dateTime('d/m/Y H:i'),
   298	
   299	                    Infolists\Components\TextEntry::make('closedBy.name')
   300	                        ->label('أغلق بواسطة')
   301	                        ->placeholder('—'),
   302	
   303	                    Infolists\Components\TextEntry::make('closed_at')
   304	                        ->label('تاريخ الإغلاق')
   305	                        ->dateTime('d/m/Y H:i')
   306	                        ->placeholder('—'),
   307	
   308	                    Infolists\Components\TextEntry::make('notes')
   309	                        ->label('ملاحظات')
   310	                        ->columnSpanFull()
   311	                        ->placeholder('—'),
   312	
   313	                    Infolists\Components\TextEntry::make('closing_notes')
   314	                        ->label('ملاحظات الإغلاق')
   315	                        ->columnSpanFull()
   316	                        ->placeholder('—'),
   317	                ])->columns(2),
   318	        ]);
   319	    }
   320	
   321	    public static function getRelations(): array
   322	    {
   323	        return [
   324	            RelationManagers\TransactionsRelationManager::class,
   325	        ];
   326	    }
   327	
   328	    public static function getPages(): array
   329	    {
   330	        return [
   331	            'index' => Pages\ListCashRegisters::route('/'),
   332	            'create' => Pages\CreateCashRegister::route('/create'),
   333	            'view' => Pages\ViewCashRegister::route('/{record}'),
   334	        ];
   335	    }
   336	
   337	    public static function getNavigationBadge(): ?string
   338	    {
   339	        return static::getModel()::where('status', CashRegisterStatus::OPEN)->count() ?: null;
   340	    }
   341	
   342	    public static function getNavigationBadgeColor(): string|array|null
   343	    {
   344	        return 'success';
   345	    }
   346	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [093]: app/Filament/Resources/CashRegisterResource.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [094/494]: app/Filament/Resources/CashRegisterResource/Pages/CreateCashRegister.php
│ LANGUAGE: php | LINES: 26 | SIZE: 727 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Filament\Resources\CashRegisterResource\Pages;
     4	
     5	use App\Filament\Resources\CashRegisterResource;
     6	use Filament\Resources\Pages\CreateRecord;
     7	use App\Enums\CashRegisterStatus;
     8	
     9	class CreateCashRegister extends CreateRecord
    10	{
    11	    protected static string $resource = CashRegisterResource::class;
    12	
    13	    protected function mutateFormDataBeforeCreate(array $data): array
    14	    {
    15	        $data['status'] = CashRegisterStatus::OPEN;
    16	        $data['opened_by'] = auth()->id();
    17	        $data['opened_at'] = now();
    18	        $data['current_balance'] = $data['opening_balance'];
    19	
    20	        return $data;
    21	    }
    22	
    23	    protected function getRedirectUrl(): string
    24	    {
    25	        return $this->getResource()::getUrl('index');
    26	    }
    27	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [094]: app/Filament/Resources/CashRegisterResource/Pages/CreateCashRegister.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [095/494]: app/Filament/Resources/CashRegisterResource/Pages/ListCashRegisters.php
│ LANGUAGE: php | LINES: 41 | SIZE: 1393 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Filament\Resources\CashRegisterResource\Pages;
     4	
     5	use App\Filament\Resources\CashRegisterResource;
     6	use App\Enums\CashRegisterStatus;
     7	use Filament\Actions;
     8	use Filament\Resources\Pages\ListRecords;
     9	use Filament\Resources\Components\Tab;
    10	
    11	class ListCashRegisters extends ListRecords
    12	{
    13	    protected static string $resource = CashRegisterResource::class;
    14	
    15	    protected function getHeaderActions(): array
    16	    {
    17	        return [
    18	            Actions\CreateAction::make(),
    19	        ];
    20	    }
    21	
    22	    public function getTabs(): array
    23	    {
    24	        return [
    25	            'all' => Tab::make('الكل')
    26	                ->icon('heroicon-o-banknotes'),
    27	
    28	            'open' => Tab::make('مفتوحة')
    29	                ->icon('heroicon-o-lock-open')
    30	                ->modifyQueryUsing(fn ($query) => $query->where('status', CashRegisterStatus::OPEN))
    31	                ->badge(fn () => \App\Models\CashRegister::where('status', CashRegisterStatus::OPEN)->count()),
    32	
    33	            'closed' => Tab::make('مغلقة')
    34	                ->icon('heroicon-o-lock-closed')
    35	                ->modifyQueryUsing(fn ($query) => $query->where('status', CashRegisterStatus::CLOSED)),
    36	
    37	            'suspended' => Tab::make('معلقة')
    38	                ->icon('heroicon-o-pause-circle')
    39	                ->modifyQueryUsing(fn ($query) => $query->where('status', CashRegisterStatus::SUSPENDED)),
    40	        ];
    41	    }
    42	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [095]: app/Filament/Resources/CashRegisterResource/Pages/ListCashRegisters.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [096/494]: app/Filament/Resources/CashRegisterResource/Pages/ViewCashRegister.php
│ LANGUAGE: php | LINES: 10 | SIZE: 274 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Filament\Resources\CashRegisterResource\Pages;
     4	
     5	use App\Filament\Resources\CashRegisterResource;
     6	use Filament\Resources\Pages\ViewRecord;
     7	
     8	class ViewCashRegister extends ViewRecord
     9	{
    10	    protected static string $resource = CashRegisterResource::class;
    11	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [096]: app/Filament/Resources/CashRegisterResource/Pages/ViewCashRegister.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [097/494]: app/Filament/Resources/CashRegisterResource/RelationManagers/TransactionsRelationManager.php
│ LANGUAGE: php | LINES: 74 | SIZE: 2534 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Filament\Resources\CashRegisterResource\RelationManagers;
     4	
     5	use Filament\Forms;
     6	use Filament\Forms\Form;
     7	use Filament\Resources\RelationManagers\RelationManager;
     8	use Filament\Tables;
     9	use Filament\Tables\Table;
    10	
    11	class TransactionsRelationManager extends RelationManager
    12	{
    13	    protected static string $relationship = 'transactions';
    14	
    15	    protected static ?string $title = 'المعاملات';
    16	
    17	    protected static ?string $modelLabel = 'معاملة';
    18	
    19	    public function table(Table $table): Table
    20	    {
    21	        return $table
    22	            ->columns([
    23	                Tables\Columns\TextColumn::make('type')
    24	                    ->label('النوع')
    25	                    ->badge()
    26	                    ->color(fn (string $state) => match ($state) {
    27	                        'in' => 'success',
    28	                        'out' => 'danger',
    29	                        default => 'gray',
    30	                    })
    31	                    ->formatStateUsing(fn (string $state) => match ($state) {
    32	                        'in' => 'وارد',
    33	                        'out' => 'صادر',
    34	                        default => $state,
    35	                    }),
    36	
    37	                Tables\Columns\TextColumn::make('amount')
    38	                    ->label('المبلغ')
    39	                    ->money('EGP')
    40	                    ->sortable(),
    41	
    42	                Tables\Columns\TextColumn::make('balance_before')
    43	                    ->label('الرصيد قبل')
    44	                    ->money('EGP')
    45	                    ->toggleable(),
    46	
    47	                Tables\Columns\TextColumn::make('balance_after')
    48	                    ->label('الرصيد بعد')
    49	                    ->money('EGP'),
    50	
    51	                Tables\Columns\TextColumn::make('description')
    52	                    ->label('الوصف')
    53	                    ->wrap()
    54	                    ->limit(60),
    55	
    56	                Tables\Columns\TextColumn::make('createdBy.name')
    57	                    ->label('بواسطة'),
    58	
    59	                Tables\Columns\TextColumn::make('created_at')
    60	                    ->label('التاريخ')
    61	                    ->dateTime('d/m/Y H:i:s')
    62	                    ->sortable(),
    63	            ])
    64	            ->defaultSort('created_at', 'desc')
    65	            ->filters([
    66	                Tables\Filters\SelectFilter::make('type')
    67	                    ->label('النوع')
    68	                    ->options([
    69	                        'in' => 'وارد',
    70	                        'out' => 'صادر',
    71	                    ]),
    72	            ])
    73	            ->paginated([10, 25, 50, 100]);
    74	    }
    75	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [097]: app/Filament/Resources/CashRegisterResource/RelationManagers/TransactionsRelationManager.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [098/494]: app/Filament/Resources/DependentResource.php
│ LANGUAGE: php | LINES: 920 | SIZE: 47593 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	declare(strict_types=1);
     4	
     5	namespace App\Filament\Resources;
     6	
     7	use App\Enums\DependentStatus;
     8	use App\Enums\Gender;
     9	use App\Filament\Resources\DependentResource\Pages;
    10	use App\Models\Dependent;
    11	use App\Models\Member;
    12	use App\Services\Membership\DependentService;
    13	use Filament\Forms;
    14	use Filament\Forms\Form;
    15	use Filament\Infolists;
    16	use Filament\Infolists\Infolist;
    17	use Filament\Notifications\Notification;
    18	use Filament\Resources\Resource;
    19	use Filament\Tables;
    20	use Filament\Tables\Table;
    21	use Illuminate\Database\Eloquent\Builder;
    22	use Illuminate\Database\Eloquent\Collection;
    23	
    24	final class DependentResource extends Resource
    25	{
    26	    protected static ?string $model = Dependent::class;
    27	
    28	    protected static ?string $navigationIcon = 'heroicon-o-user-group';
    29	
    30	    protected static ?string $navigationGroup = 'إدارة الأعضاء';
    31	
    32	    protected static ?int $navigationSort = 20;
    33	
    34	    protected static ?string $modelLabel = 'تابع';
    35	
    36	    protected static ?string $pluralModelLabel = 'التابعون';
    37	
    38	    protected static ?string $slug = 'dependents';
    39	
    40	    protected static ?string $recordTitleAttribute = 'full_name_ar';
    41	
    42	    public static function getNavigationBadge(): ?string
    43	    {
    44	        return (string) static::getModel()::where('status', DependentStatus::PENDING_REVIEW)->count();
    45	    }
    46	
    47	    public static function getNavigationBadgeColor(): string|array|null
    48	    {
    49	        $count = static::getModel()::where('status', DependentStatus::PENDING_REVIEW)->count();
    50	        return $count > 0 ? 'warning' : 'primary';
    51	    }
    52	
    53	    public static function getGloballySearchableAttributes(): array
    54	    {
    55	        return ['full_name_ar', 'full_name_en', 'national_id', 'phone', 'member.full_name_ar'];
    56	    }
    57	
    58	    public static function getGlobalSearchResultDetails(\Illuminate\Database\Eloquent\Model $record): array
    59	    {
    60	        return [
    61	            'العضو' => $record->member?->full_name_ar ?? '—',
    62	            'الحالة' => $record->status->getLabel(),
    63	            'صلة القرابة' => $record->relationship_label,
    64	        ];
    65	    }
    66	
    67	    public static function form(Form $form): Form
    68	    {
    69	        return $form->schema([
    70	            Forms\Components\Group::make()
    71	                ->schema([
    72	                    // ─── Member Selection ────────────────────────────
    73	                    Forms\Components\Section::make('بيانات العضو')
    74	                        ->description('اختر العضو المرتبط بالتابع')
    75	                        ->icon('heroicon-o-user')
    76	                        ->schema([
    77	                            Forms\Components\Select::make('member_id')
    78	                                ->label('العضو')
    79	                                ->relationship('member', 'full_name_ar')
    80	                                ->searchable(['full_name_ar', 'full_name_en', 'membership_number', 'national_id'])
    81	                                ->preload()
    82	                                ->required()
    83	                                ->live()
    84	                                ->afterStateUpdated(function (Forms\Set $set, ?int $state) {
    85	                                    if (!$state) return;
    86	                                    $member = Member::find($state);
    87	                                    if (!$member) return;
    88	
    89	                                    $service = app(DependentService::class);
    90	                                    $remaining = $service->getRemainingSlots($member);
    91	                                    if ($remaining <= 0) {
    92	                                        Notification::make()
    93	                                            ->warning()
    94	                                            ->title('تنبيه')
    95	                                            ->body('هذا العضو وصل للحد الأقصى من التابعين.')
    96	                                            ->send();
    97	                                    }
    98	                                })
    99	                                ->helperText(function (Forms\Get $get) {
   100	                                    $memberId = $get('member_id');
   101	                                    if (!$memberId) return null;
   102	                                    $member = Member::find($memberId);
   103	                                    if (!$member) return null;
   104	                                    $service = app(DependentService::class);
   105	                                    $remaining = $service->getRemainingSlots($member);
   106	                                    return "المتبقي من التابعين: {$remaining}";
   107	                                })
   108	                                ->columnSpanFull(),
   109	                        ])
   110	                        ->columns(1)
   111	                        ->collapsible(),
   112	
   113	                    // ─── Personal Information ────────────────────────
   114	                    Forms\Components\Section::make('البيانات الشخصية')
   115	                        ->description('بيانات التابع الأساسية')
   116	                        ->icon('heroicon-o-identification')
   117	                        ->schema([
   118	                            Forms\Components\TextInput::make('full_name_ar')
   119	                                ->label('الاسم بالعربية')
   120	                                ->required()
   121	                                ->maxLength(255)
   122	                                ->autofocus(),
   123	
   124	                            Forms\Components\TextInput::make('full_name_en')
   125	                                ->label('الاسم بالإنجليزية')
   126	                                ->maxLength(255)
   127	                                ->direction('ltr'),
   128	
   129	                            Forms\Components\Select::make('relationship')
   130	                                ->label('صلة القرابة')
   131	                                ->options([
   132	                                    'spouse' => 'زوج/زوجة',
   133	                                    'child' => 'ابن/ابنة',
   134	                                    'parent' => 'أب/أم',
   135	                                    'sibling' => 'أخ/أخت',
   136	                                    'grandchild' => 'حفيد/حفيدة',
   137	                                    'other' => 'أخرى',
   138	                                ])
   139	                                ->required()
   140	                                ->native(false)
   141	                                ->live(),
   142	
   143	                            Forms\Components\TextInput::make('relationship_other')
   144	                                ->label('صلة القرابة (أخرى)')
   145	                                ->maxLength(100)
   146	                                ->visible(fn (Forms\Get $get) => $get('relationship') === 'other')
   147	                                ->requiredIf('relationship', 'other'),
   148	
   149	                            Forms\Components\Select::make('gender')
   150	                                ->label('النوع')
   151	                                ->options(Gender::toFilamentOptions())
   152	                                ->required()
   153	                                ->native(false),
   154	
   155	                            Forms\Components\DatePicker::make('date_of_birth')
   156	                                ->label('تاريخ الميلاد')
   157	                                ->required()
   158	                                ->maxDate(now())
   159	                                ->displayFormat('d/m/Y')
   160	                                ->native(false)
   161	                                ->live()
   162	                                ->afterStateUpdated(function (Forms\Set $set, ?string $state) {
   163	                                    if ($state) {
   164	                                        $age = \Carbon\Carbon::parse($state)->age;
   165	                                        $set('calculated_age', $age);
   166	                                    }
   167	                                }),
   168	
   169	                            Forms\Components\Placeholder::make('calculated_age')
   170	                                ->label('العمر')
   171	                                ->content(function (Forms\Get $get) {
   172	                                    $dob = $get('date_of_birth');
   173	                                    if (!$dob) return '—';
   174	                                    return \Carbon\Carbon::parse($dob)->age . ' سنة';
   175	                                }),
   176	
   177	                            Forms\Components\TextInput::make('national_id')
   178	                                ->label('رقم الهوية / جواز السفر')
   179	                                ->maxLength(20)
   180	                                ->unique(ignoreRecord: true, modifyRuleUsing: function ($rule, Forms\Get $get) {
   181	                                    // Unique per member
   182	                                    return $rule->where('member_id', $get('member_id'));
   183	                                })
   184	                                ->direction('ltr'),
   185	                        ])
   186	                        ->columns(2),
   187	
   188	                    // ─── Contact Information ─────────────────────────
   189	                    Forms\Components\Section::make('بيانات التواصل')
   190	                        ->description('معلومات الاتصال بالتابع')
   191	                        ->icon('heroicon-o-phone')
   192	                        ->schema([
   193	                            Forms\Components\TextInput::make('phone')
   194	                                ->label('رقم الهاتف')
   195	                                ->tel()
   196	                                ->direction('ltr')
   197	                                ->maxLength(20),
   198	
   199	                            Forms\Components\TextInput::make('email')
   200	                                ->label('البريد الإلكتروني')
   201	                                ->email()
   202	                                ->direction('ltr')
   203	                                ->maxLength(255),
   204	
   205	                            Forms\Components\TextInput::make('emergency_phone')
   206	                                ->label('هاتف الطوارئ')
   207	                                ->tel()
   208	                                ->direction('ltr')
   209	                                ->maxLength(20),
   210	                        ])
   211	                        ->columns(3)
   212	                        ->collapsible(),
   213	                ])
   214	                ->columnSpan(['lg' => 2]),
   215	
   216	            // ─── Sidebar ────────────────────────────────────────────
   217	            Forms\Components\Group::make()
   218	                ->schema([
   219	                    Forms\Components\Section::make('الصورة الشخصية')
   220	                        ->icon('heroicon-o-camera')
   221	                        ->schema([
   222	                            Forms\Components\FileUpload::make('photo_path')
   223	                                ->label('الصورة')
   224	                                ->image()
   225	                                ->avatar()
   226	                                ->directory('dependents/photos')
   227	                                ->maxSize(2048)
   228	                                ->imageResizeMode('cover')
   229	                                ->imageCropAspectRatio('1:1')
   230	                                ->imageResizeTargetWidth('400')
   231	                                ->imageResizeTargetHeight('400')
   232	                                ->circleCropper()
   233	                                ->columnSpanFull(),
   234	                        ]),
   235	
   236	                    Forms\Components\Section::make('الحالة')
   237	                        ->icon('heroicon-o-signal')
   238	                        ->schema([
   239	                            Forms\Components\Select::make('status')
   240	                                ->label('الحالة')
   241	                                ->options(DependentStatus::toFilamentOptions())
   242	                                ->default(DependentStatus::PENDING_REVIEW->value)
   243	                                ->required()
   244	                                ->native(false)
   245	                                ->disabled(fn (string $operation) => $operation === 'create')
   246	                                ->dehydrated(),
   247	
   248	                            Forms\Components\Placeholder::make('approved_info')
   249	                                ->label('بيانات الموافقة')
   250	                                ->content(function (?Dependent $record) {
   251	                                    if (!$record || !$record->approved_at) return '—';
   252	                                    $approver = $record->approvedBy?->name ?? 'غير معروف';
   253	                                    return "{$approver} — {$record->approved_at->format('d/m/Y H:i')}";
   254	                                })
   255	                                ->visible(fn (?Dependent $record) => $record && $record->approved_at),
   256	                        ]),
   257	
   258	                    Forms\Components\Section::make('ملاحظات')
   259	                        ->icon('heroicon-o-chat-bubble-left-ellipsis')
   260	                        ->schema([
   261	                            Forms\Components\Textarea::make('notes')
   262	                                ->label('ملاحظات')
   263	                                ->rows(3)
   264	                                ->maxLength(1000)
   265	                                ->columnSpanFull(),
   266	
   267	                            Forms\Components\Textarea::make('medical_notes')
   268	                                ->label('ملاحظات طبية')
   269	                                ->rows(2)
   270	                                ->maxLength(500)
   271	                                ->columnSpanFull(),
   272	                        ])
   273	                        ->collapsible()
   274	                        ->collapsed(),
   275	                ])
   276	                ->columnSpan(['lg' => 1]),
   277	        ])
   278	        ->columns(3);
   279	    }
   280	
   281	    public static function table(Table $table): Table
   282	    {
   283	        return $table
   284	            ->columns([
   285	                Tables\Columns\ImageColumn::make('photo_path')
   286	                    ->label('')
   287	                    ->circular()
   288	                    ->size(40)
   289	                    ->defaultImageUrl(fn (Dependent $record) => 'https://ui-avatars.com/api/?name=' . urlencode($record->full_name_ar) . '&background=random&size=80'),
   290	
   291	                Tables\Columns\TextColumn::make('full_name_ar')
   292	                    ->label('الاسم')
   293	                    ->searchable()
   294	                    ->sortable()
   295	                    ->weight('bold')
   296	                    ->description(fn (Dependent $record) => $record->full_name_en),
   297	
   298	                Tables\Columns\TextColumn::make('member.full_name_ar')
   299	                    ->label('العضو')
   300	                    ->searchable()
   301	                    ->sortable()
   302	                    ->url(fn (Dependent $record) => $record->member_id
   303	                        ? MemberResource::getUrl('view', ['record' => $record->member_id])
   304	                        : null)
   305	                    ->color('primary')
   306	                    ->icon('heroicon-m-user'),
   307	
   308	                Tables\Columns\TextColumn::make('relationship')
   309	                    ->label('صلة القرابة')
   310	                    ->formatStateUsing(fn (string $state) => match ($state) {
   311	                        'spouse' => 'زوج/زوجة',
   312	                        'child' => 'ابن/ابنة',
   313	                        'parent' => 'أب/أم',
   314	                        'sibling' => 'أخ/أخت',
   315	                        'grandchild' => 'حفيد/حفيدة',
   316	                        'other' => 'أخرى',
   317	                        default => $state,
   318	                    })
   319	                    ->badge()
   320	                    ->color(fn (string $state) => match ($state) {
   321	                        'spouse' => 'info',
   322	                        'child' => 'success',
   323	                        'parent' => 'warning',
   324	                        'sibling' => 'primary',
   325	                        default => 'gray',
   326	                    }),
   327	
   328	                Tables\Columns\TextColumn::make('gender')
   329	                    ->label('النوع')
   330	                    ->formatStateUsing(fn ($state) => $state instanceof Gender ? $state->getLabel() : $state)
   331	                    ->toggleable(isToggledHiddenByDefault: true),
   332	
   333	                Tables\Columns\TextColumn::make('date_of_birth')
   334	                    ->label('تاريخ الميلاد')
   335	                    ->date('d/m/Y')
   336	                    ->sortable()
   337	                    ->description(fn (Dependent $record) => $record->date_of_birth ? $record->date_of_birth->age . ' سنة' : null),
   338	
   339	                Tables\Columns\TextColumn::make('national_id')
   340	                    ->label('رقم الهوية')
   341	                    ->searchable()
   342	                    ->toggleable(isToggledHiddenByDefault: true)
   343	                    ->copyable()
   344	                    ->copyMessage('تم نسخ رقم الهوية'),
   345	
   346	                Tables\Columns\TextColumn::make('phone')
   347	                    ->label('الهاتف')
   348	                    ->searchable()
   349	                    ->toggleable()
   350	                    ->copyable()
   351	                    ->icon('heroicon-m-phone'),
   352	
   353	                Tables\Columns\TextColumn::make('status')
   354	                    ->label('الحالة')
   355	                    ->badge()
   356	                    ->formatStateUsing(fn (DependentStatus $state) => $state->getLabel())
   357	                    ->color(fn (DependentStatus $state) => $state->getColor())
   358	                    ->icon(fn (DependentStatus $state) => $state->getIcon())
   359	                    ->sortable(),
   360	
   361	                Tables\Columns\TextColumn::make('approved_at')
   362	                    ->label('تاريخ الموافقة')
   363	                    ->dateTime('d/m/Y H:i')
   364	                    ->sortable()
   365	                    ->toggleable(isToggledHiddenByDefault: true),
   366	
   367	                Tables\Columns\TextColumn::make('created_at')
   368	                    ->label('تاريخ الإنشاء')
   369	                    ->dateTime('d/m/Y H:i')
   370	                    ->sortable()
   371	                    ->toggleable(isToggledHiddenByDefault: true),
   372	            ])
   373	            ->defaultSort('created_at', 'desc')
   374	            ->filters([
   375	                Tables\Filters\SelectFilter::make('status')
   376	                    ->label('الحالة')
   377	                    ->options(DependentStatus::toFilamentOptions())
   378	                    ->multiple()
   379	                    ->preload(),
   380	
   381	                Tables\Filters\SelectFilter::make('relationship')
   382	                    ->label('صلة القرابة')
   383	                    ->options([
   384	                        'spouse' => 'زوج/زوجة',
   385	                        'child' => 'ابن/ابنة',
   386	                        'parent' => 'أب/أم',
   387	                        'sibling' => 'أخ/أخت',
   388	                        'grandchild' => 'حفيد/حفيدة',
   389	                        'other' => 'أخرى',
   390	                    ])
   391	                    ->multiple(),
   392	
   393	                Tables\Filters\SelectFilter::make('gender')
   394	                    ->label('النوع')
   395	                    ->options(Gender::toFilamentOptions()),
   396	
   397	                Tables\Filters\SelectFilter::make('member_id')
   398	                    ->label('العضو')
   399	                    ->relationship('member', 'full_name_ar')
   400	                    ->searchable()
   401	                    ->preload(),
   402	
   403	                Tables\Filters\Filter::make('date_of_birth_range')
   404	                    ->label('نطاق تاريخ الميلاد')
   405	                    ->form([
   406	                        Forms\Components\DatePicker::make('dob_from')
   407	                            ->label('من'),
   408	                        Forms\Components\DatePicker::make('dob_to')
   409	                            ->label('إلى'),
   410	                    ])
   411	                    ->query(function (Builder $query, array $data): Builder {
   412	                        return $query
   413	                            ->when($data['dob_from'], fn (Builder $q, $date) => $q->where('date_of_birth', '>=', $date))
   414	                            ->when($data['dob_to'], fn (Builder $q, $date) => $q->where('date_of_birth', '<=', $date));
   415	                    }),
   416	
   417	                Tables\Filters\TernaryFilter::make('is_archived')
   418	                    ->label('مؤرشف')
   419	                    ->trueLabel('المؤرشفين فقط')
   420	                    ->falseLabel('غير المؤرشفين')
   421	                    ->default(false),
   422	
   423	                Tables\Filters\Filter::make('pending_review')
   424	                    ->label('بانتظار المراجعة')
   425	                    ->query(fn (Builder $query) => $query->where('status', DependentStatus::PENDING_REVIEW))
   426	                    ->toggle(),
   427	
   428	                Tables\Filters\Filter::make('minors')
   429	                    ->label('القُصّر (أقل من 18)')
   430	                    ->query(fn (Builder $query) => $query->where('date_of_birth', '>', now()->subYears(18)))
   431	                    ->toggle(),
   432	            ])
   433	            ->filtersFormColumns(3)
   434	            ->actions([
   435	                Tables\Actions\ActionGroup::make([
   436	                    Tables\Actions\ViewAction::make()
   437	                        ->label('عرض'),
   438	
   439	                    Tables\Actions\EditAction::make()
   440	                        ->label('تعديل'),
   441	
   442	                    Tables\Actions\Action::make('approve')
   443	                        ->label('موافقة')
   444	                        ->icon('heroicon-o-check-circle')
   445	                        ->color('success')
   446	                        ->requiresConfirmation()
   447	                        ->modalHeading('الموافقة على التابع')
   448	                        ->modalDescription(fn (Dependent $record) => "هل أنت متأكد من الموافقة على التابع: {$record->full_name_ar}؟")
   449	                        ->form([
   450	                            Forms\Components\Textarea::make('notes')
   451	                                ->label('ملاحظات (اختياري)')
   452	                                ->rows(2),
   453	                        ])
   454	                        ->action(function (Dependent $record, array $data) {
   455	                            try {
   456	                                app(DependentService::class)->approveDependent($record, $data['notes'] ?? null);
   457	                                Notification::make()
   458	                                    ->success()
   459	                                    ->title('تمت الموافقة')
   460	                                    ->body("تم الموافقة على التابع: {$record->full_name_ar}")
   461	                                    ->send();
   462	                            } catch (\Exception $e) {
   463	                                Notification::make()
   464	                                    ->danger()
   465	                                    ->title('خطأ')
   466	                                    ->body($e->getMessage())
   467	                                    ->send();
   468	                            }
   469	                        })
   470	                        ->visible(fn (Dependent $record) => in_array($record->status, [DependentStatus::PENDING_REVIEW, DependentStatus::PENDING_DOCUMENTS])),
   471	
   472	                    Tables\Actions\Action::make('reject')
   473	                        ->label('رفض')
   474	                        ->icon('heroicon-o-x-circle')
   475	                        ->color('danger')
   476	                        ->requiresConfirmation()
   477	                        ->modalHeading('رفض التابع')
   478	                        ->form([
   479	                            Forms\Components\Textarea::make('reason')
   480	                                ->label('سبب الرفض')
   481	                                ->required()
   482	                                ->rows(3),
   483	                        ])
   484	                        ->action(function (Dependent $record, array $data) {
   485	                            try {
   486	                                app(DependentService::class)->rejectDependent($record, $data['reason']);
   487	                                Notification::make()
   488	                                    ->success()
   489	                                    ->title('تم الرفض')
   490	                                    ->body("تم رفض التابع: {$record->full_name_ar}")
   491	                                    ->send();
   492	                            } catch (\Exception $e) {
   493	                                Notification::make()
   494	                                    ->danger()
   495	                                    ->title('خطأ')
   496	                                    ->body($e->getMessage())
   497	                                    ->send();
   498	                            }
   499	                        })
   500	                        ->visible(fn (Dependent $record) => in_array($record->status, [DependentStatus::PENDING_REVIEW, DependentStatus::PENDING_DOCUMENTS])),
   501	
   502	                    Tables\Actions\Action::make('request_documents')
   503	                        ->label('طلب مستندات')
   504	                        ->icon('heroicon-o-document-text')
   505	                        ->color('warning')
   506	                        ->form([
   507	                            Forms\Components\Textarea::make('notes')
   508	                                ->label('المستندات المطلوبة')
   509	                                ->required()
   510	                                ->rows(3),
   511	                        ])
   512	                        ->action(function (Dependent $record, array $data) {
   513	                            try {
   514	                                app(DependentService::class)->requestDocuments($record, $data['notes']);
   515	                                Notification::make()
   516	                                    ->success()
   517	                                    ->title('تم الطلب')
   518	                                    ->body("تم طلب مستندات إضافية من التابع: {$record->full_name_ar}")
   519	                                    ->send();
   520	                            } catch (\Exception $e) {
   521	                                Notification::make()
   522	                                    ->danger()
   523	                                    ->title('خطأ')
   524	                                    ->body($e->getMessage())
   525	                                    ->send();
   526	                            }
   527	                        })
   528	                        ->visible(fn (Dependent $record) => $record->status === DependentStatus::PENDING_REVIEW),
   529	
   530	                    Tables\Actions\Action::make('suspend')
   531	                        ->label('إيقاف')
   532	                        ->icon('heroicon-o-pause-circle')
   533	                        ->color('warning')
   534	                        ->requiresConfirmation()
   535	                        ->form([
   536	                            Forms\Components\Textarea::make('reason')
   537	                                ->label('سبب الإيقاف')
   538	                                ->required()
   539	                                ->rows(2),
   540	                        ])
   541	                        ->action(function (Dependent $record, array $data) {
   542	                            try {
   543	                                app(DependentService::class)->suspendDependent($record, $data['reason']);
   544	                                Notification::make()
   545	                                    ->success()
   546	                                    ->title('تم الإيقاف')
   547	                                    ->body("تم إيقاف التابع: {$record->full_name_ar}")
   548	                                    ->send();
   549	                            } catch (\Exception $e) {
   550	                                Notification::make()
   551	                                    ->danger()
   552	                                    ->title('خطأ')
   553	                                    ->body($e->getMessage())
   554	                                    ->send();
   555	                            }
   556	                        })
   557	                        ->visible(fn (Dependent $record) => $record->status === DependentStatus::ACTIVE),
   558	
   559	                    Tables\Actions\Action::make('reactivate')
   560	                        ->label('إعادة تفعيل')
   561	                        ->icon('heroicon-o-play-circle')
   562	                        ->color('success')
   563	                        ->requiresConfirmation()
   564	                        ->action(function (Dependent $record) {
   565	                            try {
   566	                                app(DependentService::class)->reactivateDependent($record);
   567	                                Notification::make()
   568	                                    ->success()
   569	                                    ->title('تمت إعادة التفعيل')
   570	                                    ->body("تم إعادة تفعيل التابع: {$record->full_name_ar}")
   571	                                    ->send();
   572	                            } catch (\Exception $e) {
   573	                                Notification::make()
   574	                                    ->danger()
   575	                                    ->title('خطأ')
   576	                                    ->body($e->getMessage())
   577	                                    ->send();
   578	                            }
   579	                        })
   580	                        ->visible(fn (Dependent $record) => $record->status === DependentStatus::SUSPENDED),
   581	
   582	                    Tables\Actions\Action::make('archive')
   583	                        ->label('أرشفة')
   584	                        ->icon('heroicon-o-archive-box')
   585	                        ->color('gray')
   586	                        ->requiresConfirmation()
   587	                        ->modalHeading('أرشفة التابع')
   588	                        ->modalDescription('الأرشفة ستلغي جميع الكروت النشطة للتابع.')
   589	                        ->form([
   590	                            Forms\Components\Textarea::make('reason')
   591	                                ->label('سبب الأرشفة')
   592	                                ->required()
   593	                                ->rows(2),
   594	                        ])
   595	                        ->action(function (Dependent $record, array $data) {
   596	                            try {
   597	                                app(DependentService::class)->archiveDependent($record, $data['reason']);
   598	                                Notification::make()
   599	                                    ->success()
   600	                                    ->title('تمت الأرشفة')
   601	                                    ->body("تم أرشفة التابع: {$record->full_name_ar}")
   602	                                    ->send();
   603	                            } catch (\Exception $e) {
   604	                                Notification::make()
   605	                                    ->danger()
   606	                                    ->title('خطأ')
   607	                                    ->body($e->getMessage())
   608	                                    ->send();
   609	                            }
   610	                        })
   611	                        ->visible(fn (Dependent $record) => !$record->is_archived),
   612	
   613	                    Tables\Actions\DeleteAction::make()
   614	                        ->label('حذف'),
   615	                ]),
   616	            ])
   617	            ->bulkActions([
   618	                Tables\Actions\BulkActionGroup::make([
   619	                    Tables\Actions\BulkAction::make('bulk_approve')
   620	                        ->label('موافقة جماعية')
   621	                        ->icon('heroicon-o-check-circle')
   622	                        ->color('success')
   623	                        ->requiresConfirmation()
   624	                        ->modalHeading('موافقة جماعية على التابعين')
   625	                        ->modalDescription('سيتم الموافقة على جميع التابعين المحددين الذين بحالة "بانتظار المراجعة".')
   626	                        ->deselectRecordsAfterCompletion()
   627	                        ->action(function (Collection $records) {
   628	                            $count = app(DependentService::class)->bulkApprove($records);
   629	                            Notification::make()
   630	                                ->success()
   631	                                ->title('تمت الموافقة')
   632	                                ->body("تمت الموافقة على {$count} تابع.")
   633	                                ->send();
   634	                        }),
   635	
   636	                    Tables\Actions\BulkAction::make('bulk_suspend')
   637	                        ->label('إيقاف جماعي')
   638	                        ->icon('heroicon-o-pause-circle')
   639	                        ->color('warning')
   640	                        ->requiresConfirmation()
   641	                        ->form([
   642	                            Forms\Components\Textarea::make('reason')
   643	                                ->label('سبب الإيقاف')
   644	                                ->required(),
   645	                        ])
   646	                        ->deselectRecordsAfterCompletion()
   647	                        ->action(function (Collection $records, array $data) {
   648	                            $count = 0;
   649	                            foreach ($records as $record) {
   650	                                if ($record->status === DependentStatus::ACTIVE) {
   651	                                    try {
   652	                                        app(DependentService::class)->suspendDependent($record, $data['reason']);
   653	                                        $count++;
   654	                                    } catch (\Exception) {}
   655	                                }
   656	                            }
   657	                            Notification::make()
   658	                                ->success()
   659	                                ->title('تم الإيقاف')
   660	                                ->body("تم إيقاف {$count} تابع.")
   661	                                ->send();
   662	                        }),
   663	
   664	                    Tables\Actions\DeleteBulkAction::make()
   665	                        ->label('حذف المحدد'),
   666	                ]),
   667	            ])
   668	            ->emptyStateHeading('لا يوجد تابعون')
   669	            ->emptyStateDescription('ابدأ بإضافة تابع جديد لأحد الأعضاء.')
   670	            ->emptyStateIcon('heroicon-o-user-group')
   671	            ->emptyStateActions([
   672	                Tables\Actions\CreateAction::make()
   673	                    ->label('إضافة تابع جديد')
   674	                    ->icon('heroicon-o-plus'),
   675	            ])
   676	            ->poll('30s')
   677	            ->striped();
   678	    }
   679	
   680	    public static function infolist(Infolist $infolist): Infolist
   681	    {
   682	        return $infolist->schema([
   683	            Infolists\Components\Group::make()
   684	                ->schema([
   685	                    // ─── Personal Info ───────────────────────────────
   686	                    Infolists\Components\Section::make('البيانات الشخصية')
   687	                        ->icon('heroicon-o-identification')
   688	                        ->schema([
   689	                            Infolists\Components\Grid::make(3)->schema([
   690	                                Infolists\Components\TextEntry::make('full_name_ar')
   691	                                    ->label('الاسم بالعربية')
   692	                                    ->weight('bold')
   693	                                    ->size('lg'),
   694	
   695	                                Infolists\Components\TextEntry::make('full_name_en')
   696	                                    ->label('الاسم بالإنجليزية'),
   697	
   698	                                Infolists\Components\TextEntry::make('relationship')
   699	                                    ->label('صلة القرابة')
   700	                                    ->formatStateUsing(fn (string $state) => match ($state) {
   701	                                        'spouse' => 'زوج/زوجة',
   702	                                        'child' => 'ابن/ابنة',
   703	                                        'parent' => 'أب/أم',
   704	                                        'sibling' => 'أخ/أخت',
   705	                                        'grandchild' => 'حفيد/حفيدة',
   706	                                        'other' => 'أخرى',
   707	                                        default => $state,
   708	                                    })
   709	                                    ->badge()
   710	                                    ->color('info'),
   711	                            ]),
   712	
   713	                            Infolists\Components\Grid::make(4)->schema([
   714	                                Infolists\Components\TextEntry::make('gender')
   715	                                    ->label('النوع')
   716	                                    ->formatStateUsing(fn ($state) => $state instanceof Gender ? $state->getLabel() : $state)
   717	                                    ->icon(fn ($state) => $state instanceof Gender ? $state->getIcon() : null),
   718	
   719	                                Infolists\Components\TextEntry::make('date_of_birth')
   720	                                    ->label('تاريخ الميلاد')
   721	                                    ->date('d/m/Y'),
   722	
   723	                                Infolists\Components\TextEntry::make('calculated_age')
   724	                                    ->label('العمر')
   725	                                    ->state(fn (Dependent $record) => $record->date_of_birth ? $record->date_of_birth->age . ' سنة' : '—'),
   726	
   727	                                Infolists\Components\TextEntry::make('national_id')
   728	                                    ->label('رقم الهوية')
   729	                                    ->copyable()
   730	                                    ->copyMessage('تم نسخ رقم الهوية')
   731	                                    ->icon('heroicon-m-identification')
   732	                                    ->placeholder('غير مسجل'),
   733	                            ]),
   734	                        ]),
   735	
   736	                    // ─── Contact Info ────────────────────────────────
   737	                    Infolists\Components\Section::make('بيانات التواصل')
   738	                        ->icon('heroicon-o-phone')
   739	                        ->schema([
   740	                            Infolists\Components\Grid::make(3)->schema([
   741	                                Infolists\Components\TextEntry::make('phone')
   742	                                    ->label('الهاتف')
   743	                                    ->icon('heroicon-m-phone')
   744	                                    ->copyable()
   745	                                    ->placeholder('غير مسجل'),
   746	
   747	                                Infolists\Components\TextEntry::make('email')
   748	                                    ->label('البريد الإلكتروني')
   749	                                    ->icon('heroicon-m-envelope')
   750	                                    ->copyable()
   751	                                    ->placeholder('غير مسجل'),
   752	
   753	                                Infolists\Components\TextEntry::make('emergency_phone')
   754	                                    ->label('هاتف الطوارئ')
   755	                                    ->icon('heroicon-m-phone')
   756	                                    ->placeholder('غير مسجل'),
   757	                            ]),
   758	                        ])
   759	                        ->collapsible(),
   760	
   761	                    // ─── Member Info ─────────────────────────────────
   762	                    Infolists\Components\Section::make('بيانات العضو')
   763	                        ->icon('heroicon-o-user')
   764	                        ->schema([
   765	                            Infolists\Components\Grid::make(3)->schema([
   766	                                Infolists\Components\TextEntry::make('member.full_name_ar')
   767	                                    ->label('اسم العضو')
   768	                                    ->url(fn (Dependent $record) => MemberResource::getUrl('view', ['record' => $record->member_id]))
   769	                                    ->color('primary')
   770	                                    ->weight('bold'),
   771	
   772	                                Infolists\Components\TextEntry::make('member.membership_number')
   773	                                    ->label('رقم العضوية')
   774	                                    ->copyable(),
   775	
   776	                                Infolists\Components\TextEntry::make('member.phone')
   777	                                    ->label('هاتف العضو')
   778	                                    ->icon('heroicon-m-phone'),
   779	                            ]),
   780	                        ])
   781	                        ->collapsible(),
   782	
   783	                    // ─── Notes ────────────────────────────────────────
   784	                    Infolists\Components\Section::make('الملاحظات')
   785	                        ->icon('heroicon-o-chat-bubble-left-ellipsis')
   786	                        ->schema([
   787	                            Infolists\Components\TextEntry::make('notes')
   788	                                ->label('ملاحظات عامة')
   789	                                ->placeholder('لا توجد ملاحظات')
   790	                                ->columnSpanFull(),
   791	
   792	                            Infolists\Components\TextEntry::make('medical_notes')
   793	                                ->label('ملاحظات طبية')
   794	                                ->placeholder('لا توجد ملاحظات طبية')
   795	                                ->columnSpanFull(),
   796	
   797	                            Infolists\Components\TextEntry::make('review_notes')
   798	                                ->label('ملاحظات المراجعة')
   799	                                ->placeholder('—')
   800	                                ->visible(fn (Dependent $record) => !empty($record->review_notes))
   801	                                ->columnSpanFull(),
   802	
   803	                            Infolists\Components\TextEntry::make('rejection_reason')
   804	                                ->label('سبب الرفض')
   805	                                ->color('danger')
   806	                                ->visible(fn (Dependent $record) => !empty($record->rejection_reason))
   807	                                ->columnSpanFull(),
   808	
   809	                            Infolists\Components\TextEntry::make('suspension_reason')
   810	                                ->label('سبب الإيقاف')
   811	                                ->color('warning')
   812	                                ->visible(fn (Dependent $record) => !empty($record->suspension_reason))
   813	                                ->columnSpanFull(),
   814	
   815	                            Infolists\Components\TextEntry::make('archive_reason')
   816	                                ->label('سبب الأرشفة')
   817	                                ->color('gray')
   818	                                ->visible(fn (Dependent $record) => !empty($record->archive_reason))
   819	                                ->columnSpanFull(),
   820	                        ])
   821	                        ->collapsible()
   822	                        ->collapsed(),
   823	                ])
   824	                ->columnSpan(['lg' => 2]),
   825	
   826	            // ─── Sidebar ────────────────────────────────────────────
   827	            Infolists\Components\Group::make()
   828	                ->schema([
   829	                    Infolists\Components\Section::make('الصورة')
   830	                        ->schema([
   831	                            Infolists\Components\ImageEntry::make('photo_path')
   832	                                ->label('')
   833	                                ->circular()
   834	                                ->size(150)
   835	                                ->defaultImageUrl(fn (Dependent $record) => 'https://ui-avatars.com/api/?name=' . urlencode($record->full_name_ar) . '&background=random&size=200'),
   836	                        ]),
   837	
   838	                    Infolists\Components\Section::make('الحالة')
   839	                        ->schema([
   840	                            Infolists\Components\TextEntry::make('status')
   841	                                ->label('الحالة الحالية')
   842	                                ->badge()
   843	                                ->formatStateUsing(fn (DependentStatus $state) => $state->getLabel())
   844	                                ->color(fn (DependentStatus $state) => $state->getColor())
   845	                                ->icon(fn (DependentStatus $state) => $state->getIcon())
   846	                                ->size('lg'),
   847	
   848	                            Infolists\Components\TextEntry::make('approvedBy.name')
   849	                                ->label('تمت الموافقة بواسطة')
   850	                                ->visible(fn (Dependent $record) => $record->approved_by !== null)
   851	                                ->icon('heroicon-m-user'),
   852	
   853	                            Infolists\Components\TextEntry::make('approved_at')
   854	                                ->label('تاريخ الموافقة')
   855	                                ->dateTime('d/m/Y H:i')
   856	                                ->visible(fn (Dependent $record) => $record->approved_at !== null),
   857	
   858	                            Infolists\Components\TextEntry::make('suspended_at')
   859	                                ->label('تاريخ الإيقاف')
   860	                                ->dateTime('d/m/Y H:i')
   861	                                ->visible(fn (Dependent $record) => $record->suspended_at !== null)
   862	                                ->color('warning'),
   863	
   864	                            Infolists\Components\TextEntry::make('archived_at')
   865	                                ->label('تاريخ الأرشفة')
   866	                                ->dateTime('d/m/Y H:i')
   867	                                ->visible(fn (Dependent $record) => $record->archived_at !== null)
   868	                                ->color('gray'),
   869	                        ]),
   870	
   871	                    Infolists\Components\Section::make('معلومات النظام')
   872	                        ->schema([
   873	                            Infolists\Components\TextEntry::make('createdBy.name')
   874	                                ->label('أنشأ بواسطة')
   875	                                ->icon('heroicon-m-user'),
   876	
   877	                            Infolists\Components\TextEntry::make('created_at')
   878	                                ->label('تاريخ الإنشاء')
   879	                                ->dateTime('d/m/Y H:i'),
   880	
   881	                            Infolists\Components\TextEntry::make('updatedBy.name')
   882	                                ->label('آخر تعديل بواسطة')
   883	                                ->icon('heroicon-m-user'),
   884	
   885	                            Infolists\Components\TextEntry::make('updated_at')
   886	                                ->label('آخر تعديل')
   887	                                ->dateTime('d/m/Y H:i'),
   888	                        ])
   889	                        ->collapsible()
   890	                        ->collapsed(),
   891	                ])
   892	                ->columnSpan(['lg' => 1]),
   893	        ])
   894	        ->columns(3);
   895	    }
   896	
   897	    public static function getRelations(): array
   898	    {
   899	        return [
   900	            DependentResource\RelationManagers\CardsRelationManager::class,
   901	            DependentResource\RelationManagers\DocumentsRelationManager::class,
   902	            DependentResource\RelationManagers\ActivityLogRelationManager::class,
   903	        ];
   904	    }
   905	
   906	    public static function getPages(): array
   907	    {
   908	        return [
   909	            'index' => Pages\ListDependents::route('/'),
   910	            'create' => Pages\CreateDependent::route('/create'),
   911	            'view' => Pages\ViewDependent::route('/{record}'),
   912	            'edit' => Pages\EditDependent::route('/{record}/edit'),
   913	        ];
   914	    }
   915	
   916	    public static function getEloquentQuery(): Builder
   917	    {
   918	        return parent::getEloquentQuery()
   919	            ->with(['member:id,full_name_ar,membership_number,phone', 'approvedBy:id,name']);
   920	    }
   921	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [098]: app/Filament/Resources/DependentResource.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [099/494]: app/Filament/Resources/DependentResource/Pages/CreateDependent.php
│ LANGUAGE: php | LINES: 54 | SIZE: 1608 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	declare(strict_types=1);
     4	
     5	namespace App\Filament\Resources\DependentResource\Pages;
     6	
     7	use App\Filament\Resources\DependentResource;
     8	use App\Services\Membership\DependentService;
     9	use App\Models\Member;
    10	use Filament\Notifications\Notification;
    11	use Filament\Resources\Pages\CreateRecord;
    12	
    13	final class CreateDependent extends CreateRecord
    14	{
    15	    protected static string $resource = DependentResource::class;
    16	
    17	    protected function getRedirectUrl(): string
    18	    {
    19	        return $this->getResource()::getUrl('view', ['record' => $this->record]);
    20	    }
    21	
    22	    protected function getCreatedNotificationTitle(): ?string
    23	    {
    24	        return 'تم إضافة التابع بنجاح';
    25	    }
    26	
    27	    protected function beforeCreate(): void
    28	    {
    29	        $memberId = $this->data['member_id'] ?? null;
    30	
    31	        if ($memberId) {
    32	            $member = Member::find($memberId);
    33	            if ($member) {
    34	                $service = app(DependentService::class);
    35	                if (!$service->canAddDependent($member)) {
    36	                    Notification::make()
    37	                        ->danger()
    38	                        ->title('خطأ')
    39	                        ->body('لقد تجاوز هذا العضو الحد الأقصى لعدد التابعين.')
    40	                        ->send();
    41	
    42	                    $this->halt();
    43	                }
    44	            }
    45	        }
    46	    }
    47	
    48	    protected function mutateFormDataBeforeCreate(array $data): array
    49	    {
    50	        $data['created_by'] = auth()->id();
    51	        $data['status'] = $data['status'] ?? \App\Enums\DependentStatus::PENDING_REVIEW->value;
    52	
    53	        return $data;
    54	    }
    55	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [099]: app/Filament/Resources/DependentResource/Pages/CreateDependent.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [100/494]: app/Filament/Resources/DependentResource/Pages/EditDependent.php
│ LANGUAGE: php | LINES: 41 | SIZE: 992 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	declare(strict_types=1);
     4	
     5	namespace App\Filament\Resources\DependentResource\Pages;
     6	
     7	use App\Filament\Resources\DependentResource;
     8	use Filament\Actions;
     9	use Filament\Resources\Pages\EditRecord;
    10	
    11	final class EditDependent extends EditRecord
    12	{
    13	    protected static string $resource = DependentResource::class;
    14	
    15	    protected function getHeaderActions(): array
    16	    {
    17	        return [
    18	            Actions\ViewAction::make()
    19	                ->label('عرض'),
    20	
    21	            Actions\DeleteAction::make()
    22	                ->label('حذف'),
    23	        ];
    24	    }
    25	
    26	    protected function getRedirectUrl(): string
    27	    {
    28	        return $this->getResource()::getUrl('view', ['record' => $this->record]);
    29	    }
    30	
    31	    protected function getSavedNotificationTitle(): ?string
    32	    {
    33	        return 'تم تحديث بيانات التابع بنجاح';
    34	    }
    35	
    36	    protected function mutateFormDataBeforeSave(array $data): array
    37	    {
    38	        $data['updated_by'] = auth()->id();
    39	
    40	        return $data;
    41	    }
    42	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [100]: app/Filament/Resources/DependentResource/Pages/EditDependent.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [101/494]: app/Filament/Resources/DependentResource/Pages/ListDependents.php
│ LANGUAGE: php | LINES: 73 | SIZE: 3067 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	declare(strict_types=1);
     4	
     5	namespace App\Filament\Resources\DependentResource\Pages;
     6	
     7	use App\Enums\DependentStatus;
     8	use App\Filament\Resources\DependentResource;
     9	use Filament\Actions;
    10	use Filament\Resources\Components\Tab;
    11	use Filament\Resources\Pages\ListRecords;
    12	use Illuminate\Database\Eloquent\Builder;
    13	
    14	final class ListDependents extends ListRecords
    15	{
    16	    protected static string $resource = DependentResource::class;
    17	
    18	    protected function getHeaderActions(): array
    19	    {
    20	        return [
    21	            Actions\CreateAction::make()
    22	                ->label('إضافة تابع جديد')
    23	                ->icon('heroicon-o-plus'),
    24	        ];
    25	    }
    26	
    27	    public function getTabs(): array
    28	    {
    29	        return [
    30	            'all' => Tab::make('الكل')
    31	                ->icon('heroicon-o-users')
    32	                ->badge(fn () => $this->getModel()::query()->where('is_archived', false)->count()),
    33	
    34	            'pending_review' => Tab::make('بانتظار المراجعة')
    35	                ->icon('heroicon-o-clock')
    36	                ->modifyQueryUsing(fn (Builder $query) => $query->where('status', DependentStatus::PENDING_REVIEW))
    37	                ->badge(fn () => $this->getModel()::where('status', DependentStatus::PENDING_REVIEW)->count())
    38	                ->badgeColor('warning'),
    39	
    40	            'pending_documents' => Tab::make('بانتظار المستندات')
    41	                ->icon('heroicon-o-document-text')
    42	                ->modifyQueryUsing(fn (Builder $query) => $query->where('status', DependentStatus::PENDING_DOCUMENTS))
    43	                ->badge(fn () => $this->getModel()::where('status', DependentStatus::PENDING_DOCUMENTS)->count())
    44	                ->badgeColor('info'),
    45	
    46	            'active' => Tab::make('نشط')
    47	                ->icon('heroicon-o-check-circle')
    48	                ->modifyQueryUsing(fn (Builder $query) => $query->where('status', DependentStatus::ACTIVE))
    49	                ->badge(fn () => $this->getModel()::where('status', DependentStatus::ACTIVE)->count())
    50	                ->badgeColor('success'),
    51	
    52	            'suspended' => Tab::make('موقوف')
    53	                ->icon('heroicon-o-pause-circle')
    54	                ->modifyQueryUsing(fn (Builder $query) => $query->where('status', DependentStatus::SUSPENDED))
    55	                ->badge(fn () => $this->getModel()::where('status', DependentStatus::SUSPENDED)->count())
    56	                ->badgeColor('warning'),
    57	
    58	            'rejected' => Tab::make('مرفوض')
    59	                ->icon('heroicon-o-x-circle')
    60	                ->modifyQueryUsing(fn (Builder $query) => $query->where('status', DependentStatus::REJECTED))
    61	                ->badge(fn () => $this->getModel()::where('status', DependentStatus::REJECTED)->count())
    62	                ->badgeColor('danger'),
    63	
    64	            'archived' => Tab::make('مؤرشف')
    65	                ->icon('heroicon-o-archive-box')
    66	                ->modifyQueryUsing(fn (Builder $query) => $query->where('is_archived', true)),
    67	        ];
    68	    }
    69	
    70	    public function getDefaultActiveTab(): string|int|null
    71	    {
    72	        return 'all';
    73	    }
    74	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [101]: app/Filament/Resources/DependentResource/Pages/ListDependents.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [102/494]: app/Filament/Resources/DependentResource/Pages/ViewDependent.php
│ LANGUAGE: php | LINES: 152 | SIZE: 7116 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	declare(strict_types=1);
     4	
     5	namespace App\Filament\Resources\DependentResource\Pages;
     6	
     7	use App\Enums\DependentStatus;
     8	use App\Filament\Resources\DependentResource;
     9	use App\Services\Membership\DependentService;
    10	use Filament\Actions;
    11	use Filament\Forms;
    12	use Filament\Notifications\Notification;
    13	use Filament\Resources\Pages\ViewRecord;
    14	
    15	final class ViewDependent extends ViewRecord
    16	{
    17	    protected static string $resource = DependentResource::class;
    18	
    19	    protected function getHeaderActions(): array
    20	    {
    21	        return [
    22	            Actions\EditAction::make()
    23	                ->label('تعديل')
    24	                ->icon('heroicon-o-pencil'),
    25	
    26	            Actions\Action::make('approve')
    27	                ->label('موافقة')
    28	                ->icon('heroicon-o-check-circle')
    29	                ->color('success')
    30	                ->requiresConfirmation()
    31	                ->form([
    32	                    Forms\Components\Textarea::make('notes')
    33	                        ->label('ملاحظات (اختياري)')
    34	                        ->rows(2),
    35	                ])
    36	                ->action(function (array $data) {
    37	                    try {
    38	                        app(DependentService::class)->approveDependent($this->record, $data['notes'] ?? null);
    39	                        Notification::make()->success()->title('تمت الموافقة')->send();
    40	                        $this->refreshFormData(['status', 'approved_by', 'approved_at']);
    41	                    } catch (\Exception $e) {
    42	                        Notification::make()->danger()->title('خطأ')->body($e->getMessage())->send();
    43	                    }
    44	                })
    45	                ->visible(fn () => in_array($this->record->status, [DependentStatus::PENDING_REVIEW, DependentStatus::PENDING_DOCUMENTS])),
    46	
    47	            Actions\Action::make('reject')
    48	                ->label('رفض')
    49	                ->icon('heroicon-o-x-circle')
    50	                ->color('danger')
    51	                ->requiresConfirmation()
    52	                ->form([
    53	                    Forms\Components\Textarea::make('reason')
    54	                        ->label('سبب الرفض')
    55	                        ->required()
    56	                        ->rows(3),
    57	                ])
    58	                ->action(function (array $data) {
    59	                    try {
    60	                        app(DependentService::class)->rejectDependent($this->record, $data['reason']);
    61	                        Notification::make()->success()->title('تم الرفض')->send();
    62	                        $this->refreshFormData(['status', 'rejection_reason']);
    63	                    } catch (\Exception $e) {
    64	                        Notification::make()->danger()->title('خطأ')->body($e->getMessage())->send();
    65	                    }
    66	                })
    67	                ->visible(fn () => in_array($this->record->status, [DependentStatus::PENDING_REVIEW, DependentStatus::PENDING_DOCUMENTS])),
    68	
    69	            Actions\Action::make('request_documents')
    70	                ->label('طلب مستندات')
    71	                ->icon('heroicon-o-document-text')
    72	                ->color('warning')
    73	                ->form([
    74	                    Forms\Components\Textarea::make('notes')
    75	                        ->label('المستندات المطلوبة')
    76	                        ->required()
    77	                        ->rows(3),
    78	                ])
    79	                ->action(function (array $data) {
    80	                    try {
    81	                        app(DependentService::class)->requestDocuments($this->record, $data['notes']);
    82	                        Notification::make()->success()->title('تم طلب المستندات')->send();
    83	                        $this->refreshFormData(['status', 'review_notes']);
    84	                    } catch (\Exception $e) {
    85	                        Notification::make()->danger()->title('خطأ')->body($e->getMessage())->send();
    86	                    }
    87	                })
    88	                ->visible(fn () => $this->record->status === DependentStatus::PENDING_REVIEW),
    89	
    90	            Actions\Action::make('suspend')
    91	                ->label('إيقاف')
    92	                ->icon('heroicon-o-pause-circle')
    93	                ->color('warning')
    94	                ->requiresConfirmation()
    95	                ->form([
    96	                    Forms\Components\Textarea::make('reason')
    97	                        ->label('سبب الإيقاف')
    98	                        ->required(),
    99	                ])
   100	                ->action(function (array $data) {
   101	                    try {
   102	                        app(DependentService::class)->suspendDependent($this->record, $data['reason']);
   103	                        Notification::make()->success()->title('تم الإيقاف')->send();
   104	                        $this->refreshFormData(['status', 'suspension_reason', 'suspended_at']);
   105	                    } catch (\Exception $e) {
   106	                        Notification::make()->danger()->title('خطأ')->body($e->getMessage())->send();
   107	                    }
   108	                })
   109	                ->visible(fn () => $this->record->status === DependentStatus::ACTIVE),
   110	
   111	            Actions\Action::make('reactivate')
   112	                ->label('إعادة تفعيل')
   113	                ->icon('heroicon-o-play-circle')
   114	                ->color('success')
   115	                ->requiresConfirmation()
   116	                ->action(function () {
   117	                    try {
   118	                        app(DependentService::class)->reactivateDependent($this->record);
   119	                        Notification::make()->success()->title('تمت إعادة التفعيل')->send();
   120	                        $this->refreshFormData(['status']);
   121	                    } catch (\Exception $e) {
   122	                        Notification::make()->danger()->title('خطأ')->body($e->getMessage())->send();
   123	                    }
   124	                })
   125	                ->visible(fn () => $this->record->status === DependentStatus::SUSPENDED),
   126	
   127	            Actions\Action::make('archive')
   128	                ->label('أرشفة')
   129	                ->icon('heroicon-o-archive-box')
   130	                ->color('gray')
   131	                ->requiresConfirmation()
   132	                ->modalDescription('الأرشفة ستلغي جميع الكروت النشطة للتابع.')
   133	                ->form([
   134	                    Forms\Components\Textarea::make('reason')
   135	                        ->label('سبب الأرشفة')
   136	                        ->required(),
   137	                ])
   138	                ->action(function (array $data) {
   139	                    try {
   140	                        app(DependentService::class)->archiveDependent($this->record, $data['reason']);
   141	                        Notification::make()->success()->title('تمت الأرشفة')->send();
   142	                        $this->refreshFormData(['status', 'is_archived', 'archive_reason', 'archived_at']);
   143	                    } catch (\Exception $e) {
   144	                        Notification::make()->danger()->title('خطأ')->body($e->getMessage())->send();
   145	                    }
   146	                })
   147	                ->visible(fn () => !$this->record->is_archived),
   148	
   149	            Actions\DeleteAction::make()
   150	                ->label('حذف'),
   151	        ];
   152	    }
   153	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [102]: app/Filament/Resources/DependentResource/Pages/ViewDependent.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [103/494]: app/Filament/Resources/DependentResource/RelationManagers/ActivityLogRelationManager.php
│ LANGUAGE: php | LINES: 90 | SIZE: 3422 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	declare(strict_types=1);
     4	
     5	namespace App\Filament\Resources\DependentResource\RelationManagers;
     6	
     7	use Filament\Resources\RelationManagers\RelationManager;
     8	use Filament\Tables;
     9	use Filament\Tables\Table;
    10	
    11	final class ActivityLogRelationManager extends RelationManager
    12	{
    13	    protected static string $relationship = 'activities';
    14	
    15	    protected static ?string $title = 'سجل النشاط';
    16	
    17	    protected static ?string $icon = 'heroicon-o-clock';
    18	
    19	    protected static ?string $modelLabel = 'نشاط';
    20	
    21	    protected static ?string $pluralModelLabel = 'سجل النشاط';
    22	
    23	    public function table(Table $table): Table
    24	    {
    25	        return $table
    26	            ->columns([
    27	                Tables\Columns\TextColumn::make('description')
    28	                    ->label('الوصف')
    29	                    ->wrap()
    30	                    ->searchable(),
    31	
    32	                Tables\Columns\TextColumn::make('causer.name')
    33	                    ->label('بواسطة')
    34	                    ->placeholder('النظام')
    35	                    ->icon('heroicon-m-user'),
    36	
    37	                Tables\Columns\TextColumn::make('properties')
    38	                    ->label('التفاصيل')
    39	                    ->formatStateUsing(function ($state) {
    40	                        if (!$state) return '—';
    41	                        $props = is_string($state) ? json_decode($state, true) : $state->toArray();
    42	                        $formatted = [];
    43	
    44	                        if (isset($props['action'])) {
    45	                            $formatted[] = "الإجراء: {$props['action']}";
    46	                        }
    47	                        if (isset($props['from_status'])) {
    48	                            $formatted[] = "من: {$props['from_status']}";
    49	                        }
    50	                        if (isset($props['to_status'])) {
    51	                            $formatted[] = "إلى: {$props['to_status']}";
    52	                        }
    53	                        if (isset($props['reason'])) {
    54	                            $formatted[] = "السبب: {$props['reason']}";
    55	                        }
    56	
    57	                        return implode(' | ', $formatted) ?: '—';
    58	                    })
    59	                    ->wrap()
    60	                    ->toggleable(),
    61	
    62	                Tables\Columns\TextColumn::make('created_at')
    63	                    ->label('التاريخ')
    64	                    ->dateTime('d/m/Y H:i:s')
    65	                    ->sortable(),
    66	            ])
    67	            ->defaultSort('created_at', 'desc')
    68	            ->filters([
    69	                Tables\Filters\Filter::make('date_range')
    70	                    ->form([
    71	                        \Filament\Forms\Components\DatePicker::make('from')
    72	                            ->label('من'),
    73	                        \Filament\Forms\Components\DatePicker::make('until')
    74	                            ->label('إلى'),
    75	                    ])
    76	                    ->query(function ($query, array $data) {
    77	                        return $query
    78	                            ->when($data['from'], fn ($q, $d) => $q->where('created_at', '>=', $d))
    79	                            ->when($data['until'], fn ($q, $d) => $q->where('created_at', '<=', $d));
    80	                    }),
    81	            ])
    82	            ->emptyStateHeading('لا يوجد سجل نشاط')
    83	            ->emptyStateIcon('heroicon-o-clock')
    84	            ->paginated([10, 25, 50]);
    85	    }
    86	
    87	    public function isReadOnly(): bool
    88	    {
    89	        return true;
    90	    }
    91	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [103]: app/Filament/Resources/DependentResource/RelationManagers/ActivityLogRelationManager.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [104/494]: app/Filament/Resources/DependentResource/RelationManagers/CardsRelationManager.php
│ LANGUAGE: php | LINES: 80 | SIZE: 2947 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	declare(strict_types=1);
     4	
     5	namespace App\Filament\Resources\DependentResource\RelationManagers;
     6	
     7	use App\Enums\CardStatus;
     8	use Filament\Forms;
     9	use Filament\Forms\Form;
    10	use Filament\Resources\RelationManagers\RelationManager;
    11	use Filament\Tables;
    12	use Filament\Tables\Table;
    13	
    14	final class CardsRelationManager extends RelationManager
    15	{
    16	    protected static string $relationship = 'cards';
    17	
    18	    protected static ?string $title = 'الكروت';
    19	
    20	    protected static ?string $icon = 'heroicon-o-credit-card';
    21	
    22	    protected static ?string $modelLabel = 'كرت';
    23	
    24	    protected static ?string $pluralModelLabel = 'كروت';
    25	
    26	    public function table(Table $table): Table
    27	    {
    28	        return $table
    29	            ->recordTitleAttribute('card_number')
    30	            ->columns([
    31	                Tables\Columns\TextColumn::make('card_number')
    32	                    ->label('رقم الكرت')
    33	                    ->searchable()
    34	                    ->sortable()
    35	                    ->copyable()
    36	                    ->weight('bold'),
    37	
    38	                Tables\Columns\TextColumn::make('card_type')
    39	                    ->label('النوع')
    40	                    ->badge(),
    41	
    42	                Tables\Columns\TextColumn::make('issue_date')
    43	                    ->label('تاريخ الإصدار')
    44	                    ->date('d/m/Y')
    45	                    ->sortable(),
    46	
    47	                Tables\Columns\TextColumn::make('expiry_date')
    48	                    ->label('تاريخ الانتهاء')
    49	                    ->date('d/m/Y')
    50	                    ->sortable()
    51	                    ->color(fn ($record) => $record->expiry_date?->isPast() ? 'danger' : null),
    52	
    53	                Tables\Columns\TextColumn::make('status')
    54	                    ->label('الحالة')
    55	                    ->badge()
    56	                    ->formatStateUsing(fn (CardStatus $state) => $state->getLabel())
    57	                    ->color(fn (CardStatus $state) => $state->getColor())
    58	                    ->icon(fn (CardStatus $state) => $state->getIcon()),
    59	
    60	                Tables\Columns\TextColumn::make('created_at')
    61	                    ->label('تاريخ الإنشاء')
    62	                    ->dateTime('d/m/Y H:i')
    63	                    ->toggleable(isToggledHiddenByDefault: true),
    64	            ])
    65	            ->defaultSort('created_at', 'desc')
    66	            ->filters([
    67	                Tables\Filters\SelectFilter::make('status')
    68	                    ->label('الحالة')
    69	                    ->options(CardStatus::toFilamentOptions()),
    70	            ])
    71	            ->headerActions([
    72	                // Card creation handled by Phase 5 (Cards module)
    73	            ])
    74	            ->actions([
    75	                Tables\Actions\ViewAction::make()->label('عرض'),
    76	            ])
    77	            ->emptyStateHeading('لا توجد كروت')
    78	            ->emptyStateDescription('لم يتم إصدار كروت لهذا التابع بعد.')
    79	            ->emptyStateIcon('heroicon-o-credit-card');
    80	    }
    81	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [104]: app/Filament/Resources/DependentResource/RelationManagers/CardsRelationManager.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [105/494]: app/Filament/Resources/DependentResource/RelationManagers/DocumentsRelationManager.php
│ LANGUAGE: php | LINES: 127 | SIZE: 4862 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	declare(strict_types=1);
     4	
     5	namespace App\Filament\Resources\DependentResource\RelationManagers;
     6	
     7	use Filament\Forms;
     8	use Filament\Forms\Form;
     9	use Filament\Resources\RelationManagers\RelationManager;
    10	use Filament\Tables;
    11	use Filament\Tables\Table;
    12	
    13	final class DocumentsRelationManager extends RelationManager
    14	{
    15	    protected static string $relationship = 'documents';
    16	
    17	    protected static ?string $title = 'المستندات';
    18	
    19	    protected static ?string $icon = 'heroicon-o-document-duplicate';
    20	
    21	    protected static ?string $modelLabel = 'مستند';
    22	
    23	    protected static ?string $pluralModelLabel = 'مستندات';
    24	
    25	    public function form(Form $form): Form
    26	    {
    27	        return $form->schema([
    28	            Forms\Components\Select::make('document_type_id')
    29	                ->label('نوع المستند')
    30	                ->relationship('documentType', 'name_ar')
    31	                ->required()
    32	                ->preload()
    33	                ->searchable(),
    34	
    35	            Forms\Components\TextInput::make('document_number')
    36	                ->label('رقم المستند')
    37	                ->maxLength(100),
    38	
    39	            Forms\Components\DatePicker::make('issue_date')
    40	                ->label('تاريخ الإصدار')
    41	                ->displayFormat('d/m/Y'),
    42	
    43	            Forms\Components\DatePicker::make('expiry_date')
    44	                ->label('تاريخ الانتهاء')
    45	                ->displayFormat('d/m/Y')
    46	                ->after('issue_date'),
    47	
    48	            Forms\Components\FileUpload::make('file_path')
    49	                ->label('الملف')
    50	                ->required()
    51	                ->directory('dependents/documents')
    52	                ->acceptedFileTypes(['application/pdf', 'image/jpeg', 'image/png', 'image/jpg'])
    53	                ->maxSize(5120)
    54	                ->downloadable()
    55	                ->openable()
    56	                ->columnSpanFull(),
    57	
    58	            Forms\Components\Textarea::make('notes')
    59	                ->label('ملاحظات')
    60	                ->rows(2)
    61	                ->columnSpanFull(),
    62	        ]);
    63	    }
    64	
    65	    public function table(Table $table): Table
    66	    {
    67	        return $table
    68	            ->recordTitleAttribute('document_number')
    69	            ->columns([
    70	                Tables\Columns\TextColumn::make('documentType.name_ar')
    71	                    ->label('نوع المستند')
    72	                    ->sortable()
    73	                    ->searchable(),
    74	
    75	                Tables\Columns\TextColumn::make('document_number')
    76	                    ->label('رقم المستند')
    77	                    ->searchable()
    78	                    ->placeholder('—'),
    79	
    80	                Tables\Columns\TextColumn::make('issue_date')
    81	                    ->label('تاريخ الإصدار')
    82	                    ->date('d/m/Y')
    83	                    ->placeholder('—'),
    84	
    85	                Tables\Columns\TextColumn::make('expiry_date')
    86	                    ->label('تاريخ الانتهاء')
    87	                    ->date('d/m/Y')
    88	                    ->color(fn ($record) => $record->expiry_date?->isPast() ? 'danger' : null)
    89	                    ->placeholder('—'),
    90	
    91	                Tables\Columns\IconColumn::make('is_verified')
    92	                    ->label('مُتحقق')
    93	                    ->boolean(),
    94	
    95	                Tables\Columns\TextColumn::make('created_at')
    96	                    ->label('تاريخ الرفع')
    97	                    ->dateTime('d/m/Y H:i')
    98	                    ->sortable(),
    99	            ])
   100	            ->defaultSort('created_at', 'desc')
   101	            ->headerActions([
   102	                Tables\Actions\CreateAction::make()
   103	                    ->label('رفع مستند')
   104	                    ->icon('heroicon-o-arrow-up-tray')
   105	                    ->mutateFormDataUsing(function (array $data): array {
   106	                        $data['created_by'] = auth()->id();
   107	                        $data['documentable_type'] = \App\Models\Dependent::class;
   108	                        return $data;
   109	                    }),
   110	            ])
   111	            ->actions([
   112	                Tables\Actions\ViewAction::make()->label('عرض'),
   113	                Tables\Actions\EditAction::make()->label('تعديل'),
   114	                Tables\Actions\Action::make('download')
   115	                    ->label('تحميل')
   116	                    ->icon('heroicon-o-arrow-down-tray')
   117	                    ->url(fn ($record) => $record->file_path ? asset('storage/' . $record->file_path) : null)
   118	                    ->openUrlInNewTab(),
   119	                Tables\Actions\DeleteAction::make()->label('حذف'),
   120	            ])
   121	            ->bulkActions([
   122	                Tables\Actions\DeleteBulkAction::make(),
   123	            ])
   124	            ->emptyStateHeading('لا توجد مستندات')
   125	            ->emptyStateDescription('لم يتم رفع أي مستندات لهذا التابع.')
   126	            ->emptyStateIcon('heroicon-o-document-duplicate');
   127	    }
   128	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [105]: app/Filament/Resources/DependentResource/RelationManagers/DocumentsRelationManager.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [106/494]: app/Filament/Resources/DocumentRequirementResource.php
│ LANGUAGE: php | LINES: 206 | SIZE: 8850 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Filament\Resources;
     4	
     5	use App\Filament\Resources\DocumentRequirementResource\Pages;
     6	use App\Models\DocumentRequirement;
     7	use Filament\Forms;
     8	use Filament\Forms\Form;
     9	use Filament\Resources\Resource;
    10	use Filament\Tables;
    11	use Filament\Tables\Table;
    12	use Illuminate\Database\Eloquent\Builder;
    13	
    14	class DocumentRequirementResource extends Resource
    15	{
    16	    protected static ?string $model = DocumentRequirement::class;
    17	
    18	    protected static ?string $navigationIcon = 'heroicon-o-clipboard-document-check';
    19	
    20	    protected static ?string $navigationGroup = 'البطاقات والمستندات';
    21	
    22	    protected static ?int $navigationSort = 4;
    23	
    24	    protected static ?string $modelLabel = 'متطلب مستند';
    25	
    26	    protected static ?string $pluralModelLabel = 'متطلبات المستندات';
    27	
    28	    protected static ?string $navigationLabel = 'متطلبات المستندات';
    29	
    30	    protected static ?string $slug = 'document-requirements';
    31	
    32	    public static function form(Form $form): Form
    33	    {
    34	        return $form
    35	            ->schema([
    36	                Forms\Components\Section::make('تعريف المتطلب')
    37	                    ->icon('heroicon-o-clipboard-document-check')
    38	                    ->schema([
    39	                        Forms\Components\Select::make('document_type_id')
    40	                            ->label('نوع المستند')
    41	                            ->relationship('documentType', 'name_ar', fn (Builder $query) => $query->where('is_active', true)->where('is_archived', false))
    42	                            ->required()
    43	                            ->searchable()
    44	                            ->preload(),
    45	
    46	                        Forms\Components\Select::make('workflow_stage')
    47	                            ->label('مرحلة سير العمل')
    48	                            ->options([
    49	                                'application' => 'تقديم الطلب',
    50	                                'review' => 'المراجعة',
    51	                                'interview' => 'المقابلة',
    52	                                'board_approval' => 'موافقة مجلس الإدارة',
    53	                                'payment' => 'الدفع',
    54	                                'active' => 'العضوية النشطة',
    55	                                'renewal' => 'التجديد',
    56	                                'transfer' => 'النقل',
    57	                            ])
    58	                            ->required(),
    59	
    60	                        Forms\Components\Select::make('membership_type_id')
    61	                            ->label('نوع العضوية')
    62	                            ->relationship('membershipType', 'name_ar')
    63	                            ->placeholder('جميع الأنواع')
    64	                            ->helperText('اتركه فارغاً ليُطبق على جميع أنواع العضوية')
    65	                            ->searchable()
    66	                            ->preload(),
    67	
    68	                        Forms\Components\Toggle::make('is_mandatory')
    69	                            ->label('إلزامي')
    70	                            ->default(true)
    71	                            ->helperText('هل يجب على العضو تقديم هذا المستند؟'),
    72	
    73	                        Forms\Components\TextInput::make('sort_order')
    74	                            ->label('ترتيب العرض')
    75	                            ->numeric()
    76	                            ->default(0)
    77	                            ->minValue(0),
    78	
    79	                        Forms\Components\Toggle::make('is_active')
    80	                            ->label('نشط')
    81	                            ->default(true),
    82	                    ])
    83	                    ->columns(2),
    84	            ]);
    85	    }
    86	
    87	    public static function table(Table $table): Table
    88	    {
    89	        return $table
    90	            ->columns([
    91	                Tables\Columns\TextColumn::make('documentType.name_ar')
    92	                    ->label('نوع المستند')
    93	                    ->searchable()
    94	                    ->sortable()
    95	                    ->weight('bold'),
    96	
    97	                Tables\Columns\TextColumn::make('workflow_stage')
    98	                    ->label('مرحلة العمل')
    99	                    ->badge()
   100	                    ->formatStateUsing(fn (string $state) => match ($state) {
   101	                        'application' => 'تقديم الطلب',
   102	                        'review' => 'المراجعة',
   103	                        'interview' => 'المقابلة',
   104	                        'board_approval' => 'موافقة المجلس',
   105	                        'payment' => 'الدفع',
   106	                        'active' => 'العضوية النشطة',
   107	                        'renewal' => 'التجديد',
   108	                        'transfer' => 'النقل',
   109	                        default => $state,
   110	                    })
   111	                    ->color(fn (string $state) => match ($state) {
   112	                        'application' => 'info',
   113	                        'review' => 'warning',
   114	                        'interview' => 'primary',
   115	                        'board_approval' => 'danger',
   116	                        'payment' => 'success',
   117	                        'active' => 'success',
   118	                        'renewal' => 'info',
   119	                        'transfer' => 'warning',
   120	                        default => 'gray',
   121	                    })
   122	                    ->sortable(),
   123	
   124	                Tables\Columns\TextColumn::make('membershipType.name_ar')
   125	                    ->label('نوع العضوية')
   126	                    ->placeholder('جميع الأنواع')
   127	                    ->badge()
   128	                    ->color('gray'),
   129	
   130	                Tables\Columns\IconColumn::make('is_mandatory')
   131	                    ->label('إلزامي')
   132	                    ->boolean(),
   133	
   134	                Tables\Columns\IconColumn::make('is_active')
   135	                    ->label('نشط')
   136	                    ->boolean(),
   137	
   138	                Tables\Columns\TextColumn::make('sort_order')
   139	                    ->label('الترتيب')
   140	                    ->sortable(),
   141	            ])
   142	            ->filters([
   143	                Tables\Filters\SelectFilter::make('workflow_stage')
   144	                    ->label('مرحلة العمل')
   145	                    ->options([
   146	                        'application' => 'تقديم الطلب',
   147	                        'review' => 'المراجعة',
   148	                        'interview' => 'المقابلة',
   149	                        'board_approval' => 'موافقة المجلس',
   150	                        'payment' => 'الدفع',
   151	                        'active' => 'العضوية النشطة',
   152	                        'renewal' => 'التجديد',
   153	                        'transfer' => 'النقل',
   154	                    ]),
   155	
   156	                Tables\Filters\SelectFilter::make('membership_type_id')
   157	                    ->label('نوع العضوية')
   158	                    ->relationship('membershipType', 'name_ar'),
   159	
   160	                Tables\Filters\TernaryFilter::make('is_mandatory')
   161	                    ->label('إلزامي'),
   162	
   163	                Tables\Filters\TernaryFilter::make('is_active')
   164	                    ->label('نشط'),
   165	            ])
   166	            ->actions([
   167	                Tables\Actions\EditAction::make()->label('تعديل'),
   168	                Tables\Actions\DeleteAction::make()->label('حذف'),
   169	            ])
   170	            ->bulkActions([
   171	                Tables\Actions\BulkActionGroup::make([
   172	                    Tables\Actions\DeleteBulkAction::make()->label('حذف المحدد'),
   173	                ]),
   174	            ])
   175	            ->defaultSort('sort_order')
   176	            ->reorderable('sort_order')
   177	            ->groups([
   178	                Tables\Grouping\Group::make('workflow_stage')
   179	                    ->label('مرحلة العمل')
   180	                    ->getTitleFromRecordUsing(fn ($record) => match ($record->workflow_stage) {
   181	                        'application' => 'تقديم الطلب',
   182	                        'review' => 'المراجعة',
   183	                        'interview' => 'المقابلة',
   184	                        'board_approval' => 'موافقة المجلس',
   185	                        'payment' => 'الدفع',
   186	                        'active' => 'العضوية النشطة',
   187	                        'renewal' => 'التجديد',
   188	                        'transfer' => 'النقل',
   189	                        default => $record->workflow_stage,
   190	                    }),
   191	            ]);
   192	    }
   193	
   194	    public static function getRelations(): array
   195	    {
   196	        return [];
   197	    }
   198	
   199	    public static function getPages(): array
   200	    {
   201	        return [
   202	            'index' => Pages\ListDocumentRequirements::route('/'),
   203	            'create' => Pages\CreateDocumentRequirement::route('/create'),
   204	            'edit' => Pages\EditDocumentRequirement::route('/{record}/edit'),
   205	        ];
   206	    }
   207	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [106]: app/Filament/Resources/DocumentRequirementResource.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [107/494]: app/Filament/Resources/DocumentRequirementResource/Pages/CreateDocumentRequirement.php
│ LANGUAGE: php | LINES: 20 | SIZE: 558 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Filament\Resources\DocumentRequirementResource\Pages;
     4	
     5	use App\Filament\Resources\DocumentRequirementResource;
     6	use Filament\Resources\Pages\CreateRecord;
     7	
     8	class CreateDocumentRequirement extends CreateRecord
     9	{
    10	    protected static string $resource = DocumentRequirementResource::class;
    11	
    12	    protected function getRedirectUrl(): string
    13	    {
    14	        return $this->getResource()::getUrl('index');
    15	    }
    16	
    17	    protected function getCreatedNotificationTitle(): ?string
    18	    {
    19	        return 'تم إضافة المتطلب بنجاح';
    20	    }
    21	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [107]: app/Filament/Resources/DocumentRequirementResource/Pages/CreateDocumentRequirement.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [108/494]: app/Filament/Resources/DocumentRequirementResource/Pages/EditDocumentRequirement.php
│ LANGUAGE: php | LINES: 28 | SIZE: 721 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Filament\Resources\DocumentRequirementResource\Pages;
     4	
     5	use App\Filament\Resources\DocumentRequirementResource;
     6	use Filament\Actions;
     7	use Filament\Resources\Pages\EditRecord;
     8	
     9	class EditDocumentRequirement extends EditRecord
    10	{
    11	    protected static string $resource = DocumentRequirementResource::class;
    12	
    13	    protected function getHeaderActions(): array
    14	    {
    15	        return [
    16	            Actions\DeleteAction::make()->label('حذف'),
    17	        ];
    18	    }
    19	
    20	    protected function getRedirectUrl(): string
    21	    {
    22	        return $this->getResource()::getUrl('index');
    23	    }
    24	
    25	    protected function getSavedNotificationTitle(): ?string
    26	    {
    27	        return 'تم تحديث المتطلب بنجاح';
    28	    }
    29	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [108]: app/Filament/Resources/DocumentRequirementResource/Pages/EditDocumentRequirement.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [109/494]: app/Filament/Resources/DocumentRequirementResource/Pages/ListDocumentRequirements.php
│ LANGUAGE: php | LINES: 20 | SIZE: 557 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Filament\Resources\DocumentRequirementResource\Pages;
     4	
     5	use App\Filament\Resources\DocumentRequirementResource;
     6	use Filament\Actions;
     7	use Filament\Resources\Pages\ListRecords;
     8	
     9	class ListDocumentRequirements extends ListRecords
    10	{
    11	    protected static string $resource = DocumentRequirementResource::class;
    12	
    13	    protected function getHeaderActions(): array
    14	    {
    15	        return [
    16	            Actions\CreateAction::make()
    17	                ->label('إضافة متطلب')
    18	                ->icon('heroicon-o-plus-circle'),
    19	        ];
    20	    }
    21	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [109]: app/Filament/Resources/DocumentRequirementResource/Pages/ListDocumentRequirements.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [110/494]: app/Filament/Resources/DocumentTypeResource.php
│ LANGUAGE: php | LINES: 231 | SIZE: 9063 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Filament\Resources;
     4	
     5	use App\Filament\Resources\DocumentTypeResource\Pages;
     6	use App\Models\DocumentType;
     7	use Filament\Forms;
     8	use Filament\Forms\Form;
     9	use Filament\Resources\Resource;
    10	use Filament\Tables;
    11	use Filament\Tables\Table;
    12	use Illuminate\Database\Eloquent\Builder;
    13	
    14	class DocumentTypeResource extends Resource
    15	{
    16	    protected static ?string $model = DocumentType::class;
    17	
    18	    protected static ?string $navigationIcon = 'heroicon-o-document-text';
    19	
    20	    protected static ?string $navigationGroup = 'البطاقات والمستندات';
    21	
    22	    protected static ?int $navigationSort = 3;
    23	
    24	    protected static ?string $modelLabel = 'نوع مستند';
    25	
    26	    protected static ?string $pluralModelLabel = 'أنواع المستندات';
    27	
    28	    protected static ?string $navigationLabel = 'أنواع المستندات';
    29	
    30	    protected static ?string $slug = 'document-types';
    31	
    32	    protected static ?string $recordTitleAttribute = 'name_ar';
    33	
    34	    public static function form(Form $form): Form
    35	    {
    36	        return $form
    37	            ->schema([
    38	                Forms\Components\Section::make('بيانات نوع المستند')
    39	                    ->icon('heroicon-o-document-text')
    40	                    ->schema([
    41	                        Forms\Components\TextInput::make('name_ar')
    42	                            ->label('الاسم بالعربية')
    43	                            ->required()
    44	                            ->maxLength(255),
    45	
    46	                        Forms\Components\TextInput::make('name_en')
    47	                            ->label('الاسم بالإنجليزية')
    48	                            ->maxLength(255),
    49	
    50	                        Forms\Components\TextInput::make('code')
    51	                            ->label('الكود')
    52	                            ->required()
    53	                            ->unique(ignoreRecord: true)
    54	                            ->maxLength(50)
    55	                            ->alphaDash()
    56	                            ->helperText('كود فريد لنوع المستند (مثل: national_id, passport)'),
    57	
    58	                        Forms\Components\Select::make('applies_to')
    59	                            ->label('ينطبق على')
    60	                            ->options([
    61	                                'member' => 'العضو فقط',
    62	                                'dependent' => 'التابع فقط',
    63	                                'both' => 'العضو والتابع',
    64	                            ])
    65	                            ->required()
    66	                            ->default('member'),
    67	
    68	                        Forms\Components\Textarea::make('description_ar')
    69	                            ->label('الوصف بالعربية')
    70	                            ->rows(2)
    71	                            ->maxLength(500),
    72	
    73	                        Forms\Components\Textarea::make('description_en')
    74	                            ->label('الوصف بالإنجليزية')
    75	                            ->rows(2)
    76	                            ->maxLength(500),
    77	                    ])
    78	                    ->columns(2),
    79	
    80	                Forms\Components\Section::make('قيود الملف')
    81	                    ->icon('heroicon-o-paper-clip')
    82	                    ->schema([
    83	                        Forms\Components\TextInput::make('accepted_extensions')
    84	                            ->label('الامتدادات المسموحة')
    85	                            ->placeholder('pdf,jpg,jpeg,png')
    86	                            ->helperText('افصل بين الامتدادات بفاصلة')
    87	                            ->maxLength(255),
    88	
    89	                        Forms\Components\TextInput::make('max_file_size_kb')
    90	                            ->label('الحد الأقصى للحجم (كيلوبايت)')
    91	                            ->numeric()
    92	                            ->default(5120)
    93	                            ->minValue(1)
    94	                            ->maxValue(51200)
    95	                            ->suffix('KB')
    96	                            ->helperText('5120 KB = 5 MB'),
    97	                    ])
    98	                    ->columns(2),
    99	
   100	                Forms\Components\Section::make('الإعدادات')
   101	                    ->schema([
   102	                        Forms\Components\Toggle::make('is_required')
   103	                            ->label('مطلوب افتراضياً')
   104	                            ->default(false)
   105	                            ->helperText('هل هذا المستند مطلوب بشكل افتراضي؟'),
   106	
   107	                        Forms\Components\Toggle::make('is_active')
   108	                            ->label('نشط')
   109	                            ->default(true),
   110	
   111	                        Forms\Components\TextInput::make('sort_order')
   112	                            ->label('ترتيب العرض')
   113	                            ->numeric()
   114	                            ->default(0)
   115	                            ->minValue(0),
   116	                    ])
   117	                    ->columns(3),
   118	            ]);
   119	    }
   120	
   121	    public static function table(Table $table): Table
   122	    {
   123	        return $table
   124	            ->columns([
   125	                Tables\Columns\TextColumn::make('code')
   126	                    ->label('الكود')
   127	                    ->searchable()
   128	                    ->sortable()
   129	                    ->badge()
   130	                    ->color('gray'),
   131	
   132	                Tables\Columns\TextColumn::make('name_ar')
   133	                    ->label('الاسم بالعربية')
   134	                    ->searchable()
   135	                    ->sortable()
   136	                    ->weight('bold'),
   137	
   138	                Tables\Columns\TextColumn::make('name_en')
   139	                    ->label('الاسم بالإنجليزية')
   140	                    ->searchable()
   141	                    ->toggleable(isToggledHiddenByDefault: true),
   142	
   143	                Tables\Columns\TextColumn::make('applies_to')
   144	                    ->label('ينطبق على')
   145	                    ->badge()
   146	                    ->formatStateUsing(fn (string $state) => match ($state) {
   147	                        'member' => 'العضو',
   148	                        'dependent' => 'التابع',
   149	                        'both' => 'الكل',
   150	                        default => $state,
   151	                    })
   152	                    ->color(fn (string $state) => match ($state) {
   153	                        'member' => 'primary',
   154	                        'dependent' => 'warning',
   155	                        'both' => 'success',
   156	                        default => 'gray',
   157	                    }),
   158	
   159	                Tables\Columns\TextColumn::make('accepted_extensions')
   160	                    ->label('الامتدادات')
   161	                    ->placeholder('الكل'),
   162	
   163	                Tables\Columns\TextColumn::make('max_file_size_kb')
   164	                    ->label('الحجم الأقصى')
   165	                    ->suffix(' KB')
   166	                    ->numeric(),
   167	
   168	                Tables\Columns\IconColumn::make('is_required')
   169	                    ->label('مطلوب')
   170	                    ->boolean(),
   171	
   172	                Tables\Columns\IconColumn::make('is_active')
   173	                    ->label('نشط')
   174	                    ->boolean(),
   175	
   176	                Tables\Columns\TextColumn::make('sort_order')
   177	                    ->label('الترتيب')
   178	                    ->sortable(),
   179	
   180	                Tables\Columns\TextColumn::make('documents_count')
   181	                    ->label('عدد المستندات')
   182	                    ->counts('documents')
   183	                    ->badge()
   184	                    ->color('info'),
   185	            ])
   186	            ->filters([
   187	                Tables\Filters\SelectFilter::make('applies_to')
   188	                    ->label('ينطبق على')
   189	                    ->options([
   190	                        'member' => 'العضو',
   191	                        'dependent' => 'التابع',
   192	                        'both' => 'الكل',
   193	                    ]),
   194	
   195	                Tables\Filters\TernaryFilter::make('is_required')
   196	                    ->label('مطلوب'),
   197	
   198	                Tables\Filters\TernaryFilter::make('is_active')
   199	                    ->label('نشط'),
   200	            ])
   201	            ->actions([
   202	                Tables\Actions\EditAction::make()->label('تعديل'),
   203	                Tables\Actions\DeleteAction::make()->label('حذف'),
   204	            ])
   205	            ->bulkActions([
   206	                Tables\Actions\BulkActionGroup::make([
   207	                    Tables\Actions\DeleteBulkAction::make()->label('حذف المحدد'),
   208	                ]),
   209	            ])
   210	            ->defaultSort('sort_order')
   211	            ->reorderable('sort_order');
   212	    }
   213	
   214	    public static function getRelations(): array
   215	    {
   216	        return [];
   217	    }
   218	
   219	    public static function getPages(): array
   220	    {
   221	        return [
   222	            'index' => Pages\ListDocumentTypes::route('/'),
   223	            'create' => Pages\CreateDocumentType::route('/create'),
   224	            'edit' => Pages\EditDocumentType::route('/{record}/edit'),
   225	        ];
   226	    }
   227	
   228	    public static function getEloquentQuery(): Builder
   229	    {
   230	        return parent::getEloquentQuery()->where('is_archived', false);
   231	    }
   232	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [110]: app/Filament/Resources/DocumentTypeResource.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [111/494]: app/Filament/Resources/DocumentTypeResource/Pages/CreateDocumentType.php
│ LANGUAGE: php | LINES: 20 | SIZE: 537 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Filament\Resources\DocumentTypeResource\Pages;
     4	
     5	use App\Filament\Resources\DocumentTypeResource;
     6	use Filament\Resources\Pages\CreateRecord;
     7	
     8	class CreateDocumentType extends CreateRecord
     9	{
    10	    protected static string $resource = DocumentTypeResource::class;
    11	
    12	    protected function getRedirectUrl(): string
    13	    {
    14	        return $this->getResource()::getUrl('index');
    15	    }
    16	
    17	    protected function getCreatedNotificationTitle(): ?string
    18	    {
    19	        return 'تم إضافة نوع المستند بنجاح';
    20	    }
    21	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [111]: app/Filament/Resources/DocumentTypeResource/Pages/CreateDocumentType.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [112/494]: app/Filament/Resources/DocumentTypeResource/Pages/EditDocumentType.php
│ LANGUAGE: php | LINES: 28 | SIZE: 700 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Filament\Resources\DocumentTypeResource\Pages;
     4	
     5	use App\Filament\Resources\DocumentTypeResource;
     6	use Filament\Actions;
     7	use Filament\Resources\Pages\EditRecord;
     8	
     9	class EditDocumentType extends EditRecord
    10	{
    11	    protected static string $resource = DocumentTypeResource::class;
    12	
    13	    protected function getHeaderActions(): array
    14	    {
    15	        return [
    16	            Actions\DeleteAction::make()->label('حذف'),
    17	        ];
    18	    }
    19	
    20	    protected function getRedirectUrl(): string
    21	    {
    22	        return $this->getResource()::getUrl('index');
    23	    }
    24	
    25	    protected function getSavedNotificationTitle(): ?string
    26	    {
    27	        return 'تم تحديث نوع المستند بنجاح';
    28	    }
    29	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [112]: app/Filament/Resources/DocumentTypeResource/Pages/EditDocumentType.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [113/494]: app/Filament/Resources/DocumentTypeResource/Pages/ListDocumentTypes.php
│ LANGUAGE: php | LINES: 20 | SIZE: 536 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Filament\Resources\DocumentTypeResource\Pages;
     4	
     5	use App\Filament\Resources\DocumentTypeResource;
     6	use Filament\Actions;
     7	use Filament\Resources\Pages\ListRecords;
     8	
     9	class ListDocumentTypes extends ListRecords
    10	{
    11	    protected static string $resource = DocumentTypeResource::class;
    12	
    13	    protected function getHeaderActions(): array
    14	    {
    15	        return [
    16	            Actions\CreateAction::make()
    17	                ->label('إضافة نوع مستند')
    18	                ->icon('heroicon-o-plus-circle'),
    19	        ];
    20	    }
    21	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [113]: app/Filament/Resources/DocumentTypeResource/Pages/ListDocumentTypes.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [114/494]: app/Filament/Resources/FeeStructureResource.php
│ LANGUAGE: php | LINES: 176 | SIZE: 6890 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Filament\Resources;
     4	
     5	use App\Filament\Resources\FeeStructureResource\Pages;
     6	use App\Models\FeeStructure;
     7	use App\Enums\FeeCategory;
     8	use Filament\Forms;
     9	use Filament\Forms\Form;
    10	use Filament\Resources\Resource;
    11	use Filament\Tables;
    12	use Filament\Tables\Table;
    13	use Filament\Support\Enums\FontWeight;
    14	use Illuminate\Database\Eloquent\Builder;
    15	
    16	class FeeStructureResource extends Resource
    17	{
    18	    protected static ?string $model = FeeStructure::class;
    19	
    20	    protected static ?string $navigationIcon = 'heroicon-o-currency-dollar';
    21	    protected static ?string $navigationGroup = 'المالية';
    22	    protected static ?string $navigationLabel = 'هيكل الرسوم';
    23	    protected static ?string $modelLabel = 'رسم';
    24	    protected static ?string $pluralModelLabel = 'هيكل الرسوم';
    25	    protected static ?int $navigationSort = 5;
    26	
    27	    public static function form(Form $form): Form
    28	    {
    29	        return $form
    30	            ->schema([
    31	                Forms\Components\Section::make('بيانات الرسم')
    32	                    ->schema([
    33	                        Forms\Components\Select::make('fee_category')
    34	                            ->label('تصنيف الرسم')
    35	                            ->options(FeeCategory::toFilamentOptions())
    36	                            ->required()
    37	                            ->native(false),
    38	
    39	                        Forms\Components\Select::make('membership_type_id')
    40	                            ->label('نوع العضوية')
    41	                            ->relationship('membershipType', 'name_ar')
    42	                            ->required()
    43	                            ->preload(),
    44	
    45	                        Forms\Components\TextInput::make('name_ar')
    46	                            ->label('الاسم بالعربي')
    47	                            ->required()
    48	                            ->maxLength(255),
    49	
    50	                        Forms\Components\TextInput::make('name_en')
    51	                            ->label('الاسم بالإنجليزي')
    52	                            ->maxLength(255),
    53	
    54	                        Forms\Components\TextInput::make('amount')
    55	                            ->label('المبلغ')
    56	                            ->numeric()
    57	                            ->required()
    58	                            ->prefix('جنيه'),
    59	
    60	                        Forms\Components\TextInput::make('age_from')
    61	                            ->label('العمر من')
    62	                            ->numeric()
    63	                            ->nullable()
    64	                            ->helperText('للرسوم المرتبطة بعمر التابع'),
    65	
    66	                        Forms\Components\TextInput::make('age_to')
    67	                            ->label('العمر إلى')
    68	                            ->numeric()
    69	                            ->nullable(),
    70	
    71	                        Forms\Components\DatePicker::make('effective_from')
    72	                            ->label('ساري من')
    73	                            ->required()
    74	                            ->default(now()),
    75	
    76	                        Forms\Components\DatePicker::make('effective_to')
    77	                            ->label('ساري إلى')
    78	                            ->nullable()
    79	                            ->helperText('اتركه فارغاً إذا كان مفتوحاً'),
    80	
    81	                        Forms\Components\Toggle::make('is_active')
    82	                            ->label('نشط')
    83	                            ->default(true),
    84	
    85	                        Forms\Components\Textarea::make('notes_ar')
    86	                            ->label('ملاحظات')
    87	                            ->columnSpanFull(),
    88	                    ])->columns(2),
    89	            ]);
    90	    }
    91	
    92	    public static function table(Table $table): Table
    93	    {
    94	        return $table
    95	            ->columns([
    96	                Tables\Columns\TextColumn::make('fee_category')
    97	                    ->label('التصنيف')
    98	                    ->badge()
    99	                    ->formatStateUsing(fn(string $state) => FeeCategory::tryFrom($state)?->getLabel() ?? $state),
   100	
   101	                Tables\Columns\TextColumn::make('membershipType.name_ar')
   102	                    ->label('نوع العضوية')
   103	                    ->sortable(),
   104	
   105	                Tables\Columns\TextColumn::make('name_ar')
   106	                    ->label('الاسم')
   107	                    ->searchable(),
   108	
   109	                Tables\Columns\TextColumn::make('amount')
   110	                    ->label('المبلغ')
   111	                    ->money('EGP')
   112	                    ->sortable()
   113	                    ->weight(FontWeight::Bold),
   114	
   115	                Tables\Columns\TextColumn::make('age_from')
   116	                    ->label('عمر من')
   117	                    ->placeholder('-'),
   118	
   119	                Tables\Columns\TextColumn::make('age_to')
   120	                    ->label('عمر إلى')
   121	                    ->placeholder('-'),
   122	
   123	                Tables\Columns\TextColumn::make('effective_from')
   124	                    ->label('ساري من')
   125	                    ->date('Y-m-d'),
   126	
   127	                Tables\Columns\TextColumn::make('effective_to')
   128	                    ->label('ساري إلى')
   129	                    ->date('Y-m-d')
   130	                    ->placeholder('مفتوح'),
   131	
   132	                Tables\Columns\IconColumn::make('is_active')
   133	                    ->label('نشط')
   134	                    ->boolean(),
   135	            ])
   136	            ->filters([
   137	                Tables\Filters\SelectFilter::make('fee_category')
   138	                    ->label('التصنيف')
   139	                    ->options(FeeCategory::toFilamentOptions()),
   140	
   141	                Tables\Filters\SelectFilter::make('membership_type_id')
   142	                    ->label('نوع العضوية')
   143	                    ->relationship('membershipType', 'name_ar'),
   144	
   145	                Tables\Filters\TernaryFilter::make('is_active')
   146	                    ->label('نشط'),
   147	            ])
   148	            ->actions([
   149	                Tables\Actions\EditAction::make(),
   150	                Tables\Actions\ReplicateAction::make()
   151	                    ->label('نسخ')
   152	                    ->excludeAttributes(['is_active'])
   153	                    ->mutateRecordDataUsing(function (array $data) {
   154	                        $data['name_ar'] = $data['name_ar'] . ' (نسخة)';
   155	                        $data['is_active'] = false;
   156	                        return $data;
   157	                    }),
   158	            ])
   159	            ->bulkActions([
   160	                Tables\Actions\DeleteBulkAction::make(),
   161	            ]);
   162	    }
   163	
   164	    public static function getPages(): array
   165	    {
   166	        return [
   167	            'index' => Pages\ListFeeStructures::route('/'),
   168	            'create' => Pages\CreateFeeStructure::route('/create'),
   169	            'edit' => Pages\EditFeeStructure::route('/{record}/edit'),
   170	        ];
   171	    }
   172	
   173	    public static function getEloquentQuery(): Builder
   174	    {
   175	        return parent::getEloquentQuery()->where('is_archived', false);
   176	    }
   177	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [114]: app/Filament/Resources/FeeStructureResource.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [115/494]: app/Filament/Resources/FeeStructureResource/Pages/CreateFeeStructure.php
│ LANGUAGE: php | LINES: 15 | SIZE: 395 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Filament\Resources\FeeStructureResource\Pages;
     4	
     5	use App\Filament\Resources\FeeStructureResource;
     6	use Filament\Resources\Pages\CreateRecord;
     7	
     8	class CreateFeeStructure extends CreateRecord
     9	{
    10	    protected static string $resource = FeeStructureResource::class;
    11	
    12	    protected function getRedirectUrl(): string
    13	    {
    14	        return $this->getResource()::getUrl('index');
    15	    }
    16	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [115]: app/Filament/Resources/FeeStructureResource/Pages/CreateFeeStructure.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [116/494]: app/Filament/Resources/FeeStructureResource/Pages/EditFeeStructure.php
│ LANGUAGE: php | LINES: 18 | SIZE: 428 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Filament\Resources\FeeStructureResource\Pages;
     4	
     5	use App\Filament\Resources\FeeStructureResource;
     6	use Filament\Actions;
     7	use Filament\Resources\Pages\EditRecord;
     8	
     9	class EditFeeStructure extends EditRecord
    10	{
    11	    protected static string $resource = FeeStructureResource::class;
    12	
    13	    protected function getHeaderActions(): array
    14	    {
    15	        return [
    16	            Actions\DeleteAction::make(),
    17	        ];
    18	    }
    19	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [116]: app/Filament/Resources/FeeStructureResource/Pages/EditFeeStructure.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [117/494]: app/Filament/Resources/FeeStructureResource/Pages/ListFeeStructures.php
│ LANGUAGE: php | LINES: 20 | SIZE: 516 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Filament\Resources\FeeStructureResource\Pages;
     4	
     5	use App\Filament\Resources\FeeStructureResource;
     6	use Filament\Actions;
     7	use Filament\Resources\Pages\ListRecords;
     8	
     9	class ListFeeStructures extends ListRecords
    10	{
    11	    protected static string $resource = FeeStructureResource::class;
    12	
    13	    protected function getHeaderActions(): array
    14	    {
    15	        return [
    16	            Actions\CreateAction::make()
    17	                ->label('رسم جديد')
    18	                ->icon('heroicon-o-plus'),
    19	        ];
    20	    }
    21	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [117]: app/Filament/Resources/FeeStructureResource/Pages/ListFeeStructures.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [118/494]: app/Filament/Resources/InstallmentPlanResource.php
│ LANGUAGE: php | LINES: 227 | SIZE: 10179 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Filament\Resources;
     4	
     5	use App\Filament\Resources\InstallmentPlanResource\Pages;
     6	use App\Filament\Resources\InstallmentPlanResource\RelationManagers;
     7	use App\Models\InstallmentPlan;
     8	use App\Models\Member;
     9	use App\Enums\InstallmentPlanStatus;
    10	use Filament\Forms;
    11	use Filament\Forms\Form;
    12	use Filament\Resources\Resource;
    13	use Filament\Tables;
    14	use Filament\Tables\Table;
    15	use Filament\Infolists;
    16	use Filament\Infolists\Infolist;
    17	use Filament\Support\Enums\FontWeight;
    18	use Illuminate\Database\Eloquent\Builder;
    19	
    20	class InstallmentPlanResource extends Resource
    21	{
    22	    protected static ?string $model = InstallmentPlan::class;
    23	
    24	    protected static ?string $navigationIcon = 'heroicon-o-calculator';
    25	    protected static ?string $navigationGroup = 'المالية';
    26	    protected static ?string $navigationLabel = 'خطط التقسيط';
    27	    protected static ?string $modelLabel = 'خطة تقسيط';
    28	    protected static ?string $pluralModelLabel = 'خطط التقسيط';
    29	    protected static ?int $navigationSort = 3;
    30	
    31	    public static function form(Form $form): Form
    32	    {
    33	        return $form
    34	            ->schema([
    35	                Forms\Components\Section::make('بيانات خطة التقسيط')
    36	                    ->schema([
    37	                        Forms\Components\Select::make('member_id')
    38	                            ->label('العضو')
    39	                            ->relationship('member', 'full_name_ar')
    40	                            ->searchable(['full_name_ar', 'membership_number'])
    41	                            ->getOptionLabelFromRecordUsing(fn(Member $record) => "{$record->membership_number} - {$record->full_name_ar}")
    42	                            ->required(),
    43	
    44	                        Forms\Components\Select::make('subscription_id')
    45	                            ->label('الاشتراك')
    46	                            ->relationship(
    47	                                'subscription',
    48	                                'subscription_year',
    49	                                fn(Builder $query, Forms\Get $get) => $query
    50	                                    ->where('member_id', $get('member_id'))
    51	                                    ->where('is_archived', false)
    52	                            )
    53	                            ->getOptionLabelFromRecordUsing(fn($record) => "سنة {$record->subscription_year} - {$record->amount_remaining} جنيه متبقي"),
    54	
    55	                        Forms\Components\TextInput::make('total_amount')
    56	                            ->label('المبلغ الإجمالي')
    57	                            ->numeric()
    58	                            ->required()
    59	                            ->prefix('جنيه'),
    60	
    61	                        Forms\Components\TextInput::make('down_payment')
    62	                            ->label('الدفعة المقدمة')
    63	                            ->numeric()
    64	                            ->default(0)
    65	                            ->prefix('جنيه'),
    66	
    67	                        Forms\Components\TextInput::make('number_of_installments')
    68	                            ->label('عدد الأقساط')
    69	                            ->numeric()
    70	                            ->required()
    71	                            ->minValue(2)
    72	                            ->maxValue(12),
    73	
    74	                        Forms\Components\TextInput::make('interval_days')
    75	                            ->label('الفترة بين الأقساط (أيام)')
    76	                            ->numeric()
    77	                            ->default(30)
    78	                            ->required(),
    79	
    80	                        Forms\Components\DatePicker::make('start_date')
    81	                            ->label('تاريخ البدء')
    82	                            ->default(now())
    83	                            ->required(),
    84	
    85	                        Forms\Components\Textarea::make('notes_ar')
    86	                            ->label('ملاحظات')
    87	                            ->columnSpanFull(),
    88	                    ])->columns(2),
    89	            ]);
    90	    }
    91	
    92	    public static function table(Table $table): Table
    93	    {
    94	        return $table
    95	            ->columns([
    96	                Tables\Columns\TextColumn::make('member.membership_number')
    97	                    ->label('رقم العضوية')
    98	                    ->searchable()
    99	                    ->sortable(),
   100	
   101	                Tables\Columns\TextColumn::make('member.full_name_ar')
   102	                    ->label('اسم العضو')
   103	                    ->searchable()
   104	                    ->limit(25),
   105	
   106	                Tables\Columns\TextColumn::make('subscription.subscription_year')
   107	                    ->label('سنة الاشتراك')
   108	                    ->sortable(),
   109	
   110	                Tables\Columns\TextColumn::make('total_amount')
   111	                    ->label('الإجمالي')
   112	                    ->money('EGP')
   113	                    ->sortable(),
   114	
   115	                Tables\Columns\TextColumn::make('down_payment')
   116	                    ->label('مقدم')
   117	                    ->money('EGP'),
   118	
   119	                Tables\Columns\TextColumn::make('number_of_installments')
   120	                    ->label('عدد الأقساط'),
   121	
   122	                Tables\Columns\TextColumn::make('installment_amount')
   123	                    ->label('قيمة القسط')
   124	                    ->money('EGP'),
   125	
   126	                Tables\Columns\TextColumn::make('total_paid')
   127	                    ->label('إجمالي المدفوع')
   128	                    ->getStateUsing(fn(InstallmentPlan $record) => number_format($record->total_paid, 2))
   129	                    ->suffix(' جنيه')
   130	                    ->color('success'),
   131	
   132	                Tables\Columns\TextColumn::make('progress')
   133	                    ->label('التقدم')
   134	                    ->getStateUsing(fn(InstallmentPlan $record) => $record->progress_percent . '%')
   135	                    ->badge()
   136	                    ->color(fn(InstallmentPlan $record) => $record->progress_percent >= 100 ? 'success' : ($record->progress_percent >= 50 ? 'warning' : 'danger')),
   137	
   138	                Tables\Columns\TextColumn::make('status')
   139	                    ->label('الحالة')
   140	                    ->badge()
   141	                    ->formatStateUsing(fn(string $state) => InstallmentPlanStatus::tryFrom($state)?->getLabel() ?? $state)
   142	                    ->color(fn(string $state) => InstallmentPlanStatus::tryFrom($state)?->getColor() ?? 'gray'),
   143	            ])
   144	            ->defaultSort('created_at', 'desc')
   145	            ->filters([
   146	                Tables\Filters\SelectFilter::make('status')
   147	                    ->label('الحالة')
   148	                    ->options(InstallmentPlanStatus::toFilamentOptions()),
   149	            ])
   150	            ->actions([
   151	                Tables\Actions\ViewAction::make(),
   152	
   153	                Tables\Actions\Action::make('cancel')
   154	                    ->label('إلغاء الخطة')
   155	                    ->icon('heroicon-o-x-circle')
   156	                    ->color('danger')
   157	                    ->requiresConfirmation()
   158	                    ->form([
   159	                        Forms\Components\Textarea::make('reason')
   160	                            ->label('سبب الإلغاء')
   161	                            ->required(),
   162	                    ])
   163	                    ->action(function (InstallmentPlan $record, array $data) {
   164	                        app(\App\Services\Financial\InstallmentService::class)
   165	                            ->cancelPlan($record, $data['reason']);
   166	
   167	                        \Filament\Notifications\Notification::make()
   168	                            ->title('تم إلغاء خطة التقسيط')
   169	                            ->success()
   170	                            ->send();
   171	                    })
   172	                    ->visible(fn(InstallmentPlan $record) => $record->status === 'active'),
   173	            ]);
   174	    }
   175	
   176	    public static function infolist(Infolist $infolist): Infolist
   177	    {
   178	        return $infolist
   179	            ->schema([
   180	                Infolists\Components\Section::make('بيانات الخطة')
   181	                    ->schema([
   182	                        Infolists\Components\TextEntry::make('member.membership_number')
   183	                            ->label('رقم العضوية'),
   184	                        Infolists\Components\TextEntry::make('member.full_name_ar')
   185	                            ->label('اسم العضو'),
   186	                        Infolists\Components\TextEntry::make('subscription.subscription_year')
   187	                            ->label('سنة الاشتراك'),
   188	                        Infolists\Components\TextEntry::make('status')
   189	                            ->label('الحالة')
   190	                            ->badge()
   191	                            ->formatStateUsing(fn(string $state) => InstallmentPlanStatus::tryFrom($state)?->getLabel() ?? $state)
   192	                            ->color(fn(string $state) => InstallmentPlanStatus::tryFrom($state)?->getColor() ?? 'gray'),
   193	                        Infolists\Components\TextEntry::make('total_amount')
   194	                            ->label('الإجمالي')
   195	                            ->money('EGP'),
   196	                        Infolists\Components\TextEntry::make('down_payment')
   197	                            ->label('الدفعة المقدمة')
   198	                            ->money('EGP'),
   199	                        Infolists\Components\TextEntry::make('number_of_installments')
   200	                            ->label('عدد الأقساط'),
   201	                        Infolists\Components\TextEntry::make('installment_amount')
   202	                            ->label('قيمة القسط')
   203	                            ->money('EGP'),
   204	                    ])->columns(4),
   205	            ]);
   206	    }
   207	
   208	    public static function getRelations(): array
   209	    {
   210	        return [
   211	            RelationManagers\InstallmentsRelationManager::class,
   212	        ];
   213	    }
   214	
   215	    public static function getPages(): array
   216	    {
   217	        return [
   218	            'index' => Pages\ListInstallmentPlans::route('/'),
   219	            'create' => Pages\CreateInstallmentPlan::route('/create'),
   220	            'view' => Pages\ViewInstallmentPlan::route('/{record}'),
   221	        ];
   222	    }
   223	
   224	    public static function getEloquentQuery(): Builder
   225	    {
   226	        return parent::getEloquentQuery()->where('is_archived', false);
   227	    }
   228	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [118]: app/Filament/Resources/InstallmentPlanResource.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [119/494]: app/Filament/Resources/InstallmentPlanResource/Pages/CreateInstallmentPlan.php
│ LANGUAGE: php | LINES: 32 | SIZE: 926 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Filament\Resources\InstallmentPlanResource\Pages;
     4	
     5	use App\Filament\Resources\InstallmentPlanResource;
     6	use App\Services\Financial\InstallmentService;
     7	use Filament\Resources\Pages\CreateRecord;
     8	use Filament\Notifications\Notification;
     9	
    10	class CreateInstallmentPlan extends CreateRecord
    11	{
    12	    protected static string $resource = InstallmentPlanResource::class;
    13	
    14	    protected function handleRecordCreation(array $data): \Illuminate\Database\Eloquent\Model
    15	    {
    16	        try {
    17	            return app(InstallmentService::class)->createPlan($data);
    18	        } catch (\RuntimeException $e) {
    19	            Notification::make()
    20	                ->title('خطأ')
    21	                ->body($e->getMessage())
    22	                ->danger()
    23	                ->send();
    24	
    25	            $this->halt();
    26	        }
    27	    }
    28	
    29	    protected function getRedirectUrl(): string
    30	    {
    31	        return $this->getResource()::getUrl('index');
    32	    }
    33	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [119]: app/Filament/Resources/InstallmentPlanResource/Pages/CreateInstallmentPlan.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [120/494]: app/Filament/Resources/InstallmentPlanResource/Pages/ListInstallmentPlans.php
│ LANGUAGE: php | LINES: 20 | SIZE: 541 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Filament\Resources\InstallmentPlanResource\Pages;
     4	
     5	use App\Filament\Resources\InstallmentPlanResource;
     6	use Filament\Actions;
     7	use Filament\Resources\Pages\ListRecords;
     8	
     9	class ListInstallmentPlans extends ListRecords
    10	{
    11	    protected static string $resource = InstallmentPlanResource::class;
    12	
    13	    protected function getHeaderActions(): array
    14	    {
    15	        return [
    16	            Actions\CreateAction::make()
    17	                ->label('خطة تقسيط جديدة')
    18	                ->icon('heroicon-o-plus'),
    19	        ];
    20	    }
    21	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [120]: app/Filament/Resources/InstallmentPlanResource/Pages/ListInstallmentPlans.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [121/494]: app/Filament/Resources/InstallmentPlanResource/Pages/ViewInstallmentPlan.php
│ LANGUAGE: php | LINES: 10 | SIZE: 286 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Filament\Resources\InstallmentPlanResource\Pages;
     4	
     5	use App\Filament\Resources\InstallmentPlanResource;
     6	use Filament\Resources\Pages\ViewRecord;
     7	
     8	class ViewInstallmentPlan extends ViewRecord
     9	{
    10	    protected static string $resource = InstallmentPlanResource::class;
    11	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [121]: app/Filament/Resources/InstallmentPlanResource/Pages/ViewInstallmentPlan.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [122/494]: app/Filament/Resources/InstallmentPlanResource/RelationManagers/InstallmentsRelationManager.php
│ LANGUAGE: php | LINES: 96 | SIZE: 3996 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Filament\Resources\InstallmentPlanResource\RelationManagers;
     4	
     5	use App\Enums\InstallmentStatus;
     6	use Filament\Forms;
     7	use Filament\Resources\RelationManagers\RelationManager;
     8	use Filament\Tables;
     9	use Filament\Tables\Table;
    10	use Filament\Support\Enums\FontWeight;
    11	
    12	class InstallmentsRelationManager extends RelationManager
    13	{
    14	    protected static string $relationship = 'installments';
    15	    protected static ?string $title = 'الأقساط';
    16	    protected static ?string $modelLabel = 'قسط';
    17	    protected static ?string $pluralModelLabel = 'الأقساط';
    18	
    19	    public function table(Table $table): Table
    20	    {
    21	        return $table
    22	            ->columns([
    23	                Tables\Columns\TextColumn::make('installment_number')
    24	                    ->label('#')
    25	                    ->sortable(),
    26	
    27	                Tables\Columns\TextColumn::make('amount')
    28	                    ->label('المبلغ')
    29	                    ->money('EGP'),
    30	
    31	                Tables\Columns\TextColumn::make('amount_paid')
    32	                    ->label('المدفوع')
    33	                    ->money('EGP')
    34	                    ->color('success'),
    35	
    36	                Tables\Columns\TextColumn::make('amount_remaining')
    37	                    ->label('المتبقي')
    38	                    ->money('EGP')
    39	                    ->color(fn(string $state) => (float) $state > 0 ? 'danger' : 'success'),
    40	
    41	                Tables\Columns\TextColumn::make('due_date')
    42	                    ->label('تاريخ الاستحقاق')
    43	                    ->date('Y-m-d')
    44	                    ->color(fn($record) => $record->isOverdue() ? 'danger' : null),
    45	
    46	                Tables\Columns\TextColumn::make('paid_at')
    47	                    ->label('تاريخ السداد')
    48	                    ->dateTime('Y-m-d H:i'),
    49	
    50	                Tables\Columns\TextColumn::make('status')
    51	                    ->label('الحالة')
    52	                    ->badge()
    53	                    ->formatStateUsing(fn(string $state) => InstallmentStatus::tryFrom($state)?->getLabel() ?? $state)
    54	                    ->color(fn(string $state) => InstallmentStatus::tryFrom($state)?->getColor() ?? 'gray'),
    55	            ])
    56	            ->defaultSort('installment_number')
    57	            ->actions([
    58	                Tables\Actions\Action::make('pay')
    59	                    ->label('سداد')
    60	                    ->icon('heroicon-o-banknotes')
    61	                    ->color('success')
    62	                    ->form([
    63	                        Forms\Components\TextInput::make('amount')
    64	                            ->label('المبلغ')
    65	                            ->numeric()
    66	                            ->required()
    67	                            ->default(fn($record) => $record->amount_remaining),
    68	
    69	                        Forms\Components\Select::make('payment_method')
    70	                            ->label('طريقة الدفع')
    71	                            ->options([
    72	                                'cash' => 'نقدي',
    73	                                'bank_transfer' => 'تحويل بنكي',
    74	                                'check' => 'شيك',
    75	                            ])
    76	                            ->default('cash')
    77	                            ->required(),
    78	                    ])
    79	                    ->action(function ($record, array $data) {
    80	                        $result = app(\App\Services\Financial\InstallmentService::class)
    81	                            ->payInstallment($record, (float) $data['amount'], $data);
    82	
    83	                        \Filament\Notifications\Notification::make()
    84	                            ->title('تم سداد القسط')
    85	                            ->body("إيصال رقم: {$result['receipt']->receipt_number}")
    86	                            ->success()
    87	                            ->send();
    88	                    })
    89	                    ->visible(fn($record) => $record->amount_remaining > 0 && $record->status !== 'cancelled'),
    90	            ]);
    91	    }
    92	
    93	    public function isReadOnly(): bool
    94	    {
    95	        return false;
    96	    }
    97	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [122]: app/Filament/Resources/InstallmentPlanResource/RelationManagers/InstallmentsRelationManager.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [123/494]: app/Filament/Resources/MemberCardResource.php
│ LANGUAGE: php | LINES: 518 | SIZE: 24762 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Filament\Resources;
     4	
     5	use App\Enums\CardStatus;
     6	use App\Filament\Resources\MemberCardResource\Pages;
     7	use App\Models\Member;
     8	use App\Models\MemberCard;
     9	use App\Services\Cards\CardService;
    10	use Filament\Forms;
    11	use Filament\Forms\Form;
    12	use Filament\Infolists;
    13	use Filament\Infolists\Infolist;
    14	use Filament\Notifications\Notification;
    15	use Filament\Resources\Resource;
    16	use Filament\Tables;
    17	use Filament\Tables\Table;
    18	use Illuminate\Database\Eloquent\Builder;
    19	use Illuminate\Database\Eloquent\Collection;
    20	
    21	class MemberCardResource extends Resource
    22	{
    23	    protected static ?string $model = MemberCard::class;
    24	
    25	    protected static ?string $navigationIcon = 'heroicon-o-identification';
    26	
    27	    protected static ?string $navigationGroup = 'البطاقات والمستندات';
    28	
    29	    protected static ?int $navigationSort = 1;
    30	
    31	    protected static ?string $modelLabel = 'كارنيه';
    32	
    33	    protected static ?string $pluralModelLabel = 'الكروت';
    34	
    35	    protected static ?string $navigationLabel = 'كروت العضوية';
    36	
    37	    protected static ?string $slug = 'member-cards';
    38	
    39	    protected static ?string $recordTitleAttribute = 'card_number';
    40	
    41	    public static function getNavigationBadge(): ?string
    42	    {
    43	        return static::getModel()::where('status', CardStatus::ACTIVE->value)
    44	            ->where('is_archived', false)
    45	            ->count() ?: null;
    46	    }
    47	
    48	    public static function getNavigationBadgeColor(): string|array|null
    49	    {
    50	        return 'primary';
    51	    }
    52	
    53	    public static function form(Form $form): Form
    54	    {
    55	        return $form
    56	            ->schema([
    57	                Forms\Components\Section::make('بيانات الكارنيه')
    58	                    ->description('بيانات إصدار كارنيه العضوية')
    59	                    ->icon('heroicon-o-identification')
    60	                    ->schema([
    61	                        Forms\Components\Select::make('member_id')
    62	                            ->label('العضو')
    63	                            ->relationship('member', 'full_name_ar', fn (Builder $query) => $query->where('is_archived', false))
    64	                            ->getOptionLabelFromRecordUsing(fn (Member $record) => "{$record->membership_number} - {$record->full_name_ar}")
    65	                            ->searchable(['full_name_ar', 'full_name_en', 'membership_number', 'national_id'])
    66	                            ->preload()
    67	                            ->required()
    68	                            ->live()
    69	                            ->afterStateUpdated(function (Forms\Set $set, ?string $state) {
    70	                                if ($state) {
    71	                                    $member = Member::find($state);
    72	                                    if ($member) {
    73	                                        $set('photo_path', $member->photo_path);
    74	                                    }
    75	                                }
    76	                            })
    77	                            ->columnSpanFull(),
    78	
    79	                        Forms\Components\Select::make('card_type')
    80	                            ->label('نوع الكارنيه')
    81	                            ->options([
    82	                                'main' => 'أساسي',
    83	                                'replacement' => 'بدل فاقد/تالف',
    84	                                'renewal' => 'تجديد',
    85	                            ])
    86	                            ->required()
    87	                            ->default('main')
    88	                            ->live(),
    89	
    90	                        Forms\Components\Select::make('previous_card_id')
    91	                            ->label('الكارنيه السابق')
    92	                            ->options(function (Forms\Get $get) {
    93	                                $memberId = $get('member_id');
    94	                                if (!$memberId) {
    95	                                    return [];
    96	                                }
    97	                                return MemberCard::where('member_id', $memberId)
    98	                                    ->whereNot('status', CardStatus::CANCELLED->value)
    99	                                    ->pluck('card_number', 'id')
   100	                                    ->toArray();
   101	                            })
   102	                            ->searchable()
   103	                            ->visible(fn (Forms\Get $get) => in_array($get('card_type'), ['replacement', 'renewal']))
   104	                            ->required(fn (Forms\Get $get) => $get('card_type') === 'replacement'),
   105	
   106	                        Forms\Components\Textarea::make('replacement_reason')
   107	                            ->label('سبب الاستبدال')
   108	                            ->rows(2)
   109	                            ->visible(fn (Forms\Get $get) => $get('card_type') === 'replacement')
   110	                            ->required(fn (Forms\Get $get) => $get('card_type') === 'replacement')
   111	                            ->maxLength(500),
   112	
   113	                        Forms\Components\Select::make('status')
   114	                            ->label('الحالة')
   115	                            ->options(CardStatus::toFilamentOptions())
   116	                            ->default(CardStatus::ACTIVE->value)
   117	                            ->required()
   118	                            ->visibleOn('edit'),
   119	
   120	                        Forms\Components\DatePicker::make('issue_date')
   121	                            ->label('تاريخ الإصدار')
   122	                            ->default(now())
   123	                            ->required(),
   124	
   125	                        Forms\Components\DatePicker::make('expiry_date')
   126	                            ->label('تاريخ الانتهاء')
   127	                            ->after('issue_date')
   128	                            ->required(),
   129	                    ])
   130	                    ->columns(2),
   131	
   132	                Forms\Components\Section::make('التكلفة والملاحظات')
   133	                    ->schema([
   134	                        Forms\Components\TextInput::make('printing_cost')
   135	                            ->label('تكلفة الطباعة')
   136	                            ->numeric()
   137	                            ->prefix('ج.م')
   138	                            ->default(0)
   139	                            ->minValue(0),
   140	
   141	                        Forms\Components\Textarea::make('notes')
   142	                            ->label('ملاحظات')
   143	                            ->rows(3)
   144	                            ->maxLength(1000)
   145	                            ->columnSpanFull(),
   146	                    ])
   147	                    ->columns(2)
   148	                    ->collapsible(),
   149	            ]);
   150	    }
   151	
   152	    public static function table(Table $table): Table
   153	    {
   154	        return $table
   155	            ->columns([
   156	                Tables\Columns\TextColumn::make('card_number')
   157	                    ->label('رقم الكارنيه')
   158	                    ->searchable()
   159	                    ->sortable()
   160	                    ->copyable()
   161	                    ->weight('bold')
   162	                    ->color('primary'),
   163	
   164	                Tables\Columns\TextColumn::make('member.full_name_ar')
   165	                    ->label('العضو')
   166	                    ->searchable()
   167	                    ->sortable()
   168	                    ->description(fn (MemberCard $record) => $record->member?->membership_number),
   169	
   170	                Tables\Columns\ImageColumn::make('photo_path')
   171	                    ->label('الصورة')
   172	                    ->disk('public')
   173	                    ->circular()
   174	                    ->defaultImageUrl(asset('images/default-avatar.png'))
   175	                    ->size(40),
   176	
   177	                Tables\Columns\TextColumn::make('card_type')
   178	                    ->label('النوع')
   179	                    ->badge()
   180	                    ->formatStateUsing(fn (string $state) => match ($state) {
   181	                        'main' => 'أساسي',
   182	                        'replacement' => 'بدل فاقد',
   183	                        'renewal' => 'تجديد',
   184	                        default => $state,
   185	                    })
   186	                    ->color(fn (string $state) => match ($state) {
   187	                        'main' => 'primary',
   188	                        'replacement' => 'warning',
   189	                        'renewal' => 'info',
   190	                        default => 'gray',
   191	                    }),
   192	
   193	                Tables\Columns\TextColumn::make('status')
   194	                    ->label('الحالة')
   195	                    ->badge()
   196	                    ->formatStateUsing(fn (string $state) => CardStatus::tryFrom($state)?->getLabel() ?? $state)
   197	                    ->color(fn (string $state) => CardStatus::tryFrom($state)?->getColor() ?? 'gray'),
   198	
   199	                Tables\Columns\TextColumn::make('issue_date')
   200	                    ->label('تاريخ الإصدار')
   201	                    ->date('Y-m-d')
   202	                    ->sortable(),
   203	
   204	                Tables\Columns\TextColumn::make('expiry_date')
   205	                    ->label('تاريخ الانتهاء')
   206	                    ->date('Y-m-d')
   207	                    ->sortable()
   208	                    ->color(fn (MemberCard $record) => $record->expiry_date && $record->expiry_date < now()->toDateString() ? 'danger' : 'success'),
   209	
   210	                Tables\Columns\IconColumn::make('printed_at')
   211	                    ->label('مطبوع')
   212	                    ->boolean()
   213	                    ->getStateUsing(fn (MemberCard $record) => !is_null($record->printed_at)),
   214	
   215	                Tables\Columns\IconColumn::make('collected_at')
   216	                    ->label('مستلم')
   217	                    ->boolean()
   218	                    ->getStateUsing(fn (MemberCard $record) => !is_null($record->collected_at)),
   219	
   220	                Tables\Columns\TextColumn::make('printing_cost')
   221	                    ->label('التكلفة')
   222	                    ->money('EGP')
   223	                    ->sortable()
   224	                    ->toggleable(isToggledHiddenByDefault: true),
   225	
   226	                Tables\Columns\TextColumn::make('created_at')
   227	                    ->label('تاريخ الإنشاء')
   228	                    ->dateTime('Y-m-d H:i')
   229	                    ->sortable()
   230	                    ->toggleable(isToggledHiddenByDefault: true),
   231	            ])
   232	            ->filters([
   233	                Tables\Filters\SelectFilter::make('status')
   234	                    ->label('الحالة')
   235	                    ->options(CardStatus::toFilamentOptions())
   236	                    ->multiple(),
   237	
   238	                Tables\Filters\SelectFilter::make('card_type')
   239	                    ->label('نوع الكارنيه')
   240	                    ->options([
   241	                        'main' => 'أساسي',
   242	                        'replacement' => 'بدل فاقد',
   243	                        'renewal' => 'تجديد',
   244	                    ]),
   245	
   246	                Tables\Filters\Filter::make('expired')
   247	                    ->label('منتهي الصلاحية')
   248	                    ->query(fn (Builder $query) => $query->where('expiry_date', '<', now()->toDateString()))
   249	                    ->toggle(),
   250	
   251	                Tables\Filters\Filter::make('not_printed')
   252	                    ->label('غير مطبوع')
   253	                    ->query(fn (Builder $query) => $query->whereNull('printed_at')->where('status', CardStatus::ACTIVE->value))
   254	                    ->toggle(),
   255	
   256	                Tables\Filters\Filter::make('not_collected')
   257	                    ->label('غير مستلم')
   258	                    ->query(fn (Builder $query) => $query->whereNull('collected_at')->whereNotNull('printed_at'))
   259	                    ->toggle(),
   260	
   261	                Tables\Filters\Filter::make('issue_date_range')
   262	                    ->form([
   263	                        Forms\Components\DatePicker::make('issued_from')->label('من تاريخ'),
   264	                        Forms\Components\DatePicker::make('issued_until')->label('إلى تاريخ'),
   265	                    ])
   266	                    ->query(function (Builder $query, array $data) {
   267	                        return $query
   268	                            ->when($data['issued_from'], fn ($q, $d) => $q->where('issue_date', '>=', $d))
   269	                            ->when($data['issued_until'], fn ($q, $d) => $q->where('issue_date', '<=', $d));
   270	                    })
   271	                    ->columns(2),
   272	            ])
   273	            ->actions([
   274	                Tables\Actions\ActionGroup::make([
   275	                    Tables\Actions\ViewAction::make()->label('عرض'),
   276	                    Tables\Actions\EditAction::make()->label('تعديل'),
   277	
   278	                    Tables\Actions\Action::make('print_preview')
   279	                        ->label('معاينة الطباعة')
   280	                        ->icon('heroicon-o-printer')
   281	                        ->color('info')
   282	                        ->url(fn (MemberCard $record) => route('filament.admin.pages.card-preview', ['card' => $record->id]))
   283	                        ->openUrlInNewTab(),
   284	
   285	                    Tables\Actions\Action::make('mark_printed')
   286	                        ->label('تم الطباعة')
   287	                        ->icon('heroicon-o-printer')
   288	                        ->color('success')
   289	                        ->requiresConfirmation()
   290	                        ->modalHeading('تأكيد الطباعة')
   291	                        ->modalDescription('هل تم طباعة هذا الكارنيه بنجاح؟')
   292	                        ->visible(fn (MemberCard $record) => $record->status === CardStatus::ACTIVE->value && is_null($record->printed_at))
   293	                        ->action(function (MemberCard $record) {
   294	                            app(CardService::class)->markAsPrinted($record);
   295	                            Notification::make()->title('تم تسجيل الطباعة بنجاح')->success()->send();
   296	                        }),
   297	
   298	                    Tables\Actions\Action::make('mark_collected')
   299	                        ->label('تم الاستلام')
   300	                        ->icon('heroicon-o-hand-raised')
   301	                        ->color('success')
   302	                        ->requiresConfirmation()
   303	                        ->modalHeading('تأكيد الاستلام')
   304	                        ->modalDescription('هل استلم العضو هذا الكارنيه؟')
   305	                        ->visible(fn (MemberCard $record) => in_array($record->status, [CardStatus::PRINTED->value, CardStatus::ACTIVE->value]) && is_null($record->collected_at))
   306	                        ->action(function (MemberCard $record) {
   307	                            app(CardService::class)->markAsCollected($record);
   308	                            Notification::make()->title('تم تسجيل الاستلام بنجاح')->success()->send();
   309	                        }),
   310	
   311	                    Tables\Actions\Action::make('suspend_card')
   312	                        ->label('تعليق الكارنيه')
   313	                        ->icon('heroicon-o-pause-circle')
   314	                        ->color('warning')
   315	                        ->requiresConfirmation()
   316	                        ->form([
   317	                            Forms\Components\Textarea::make('reason')
   318	                                ->label('سبب التعليق')
   319	                                ->required()
   320	                                ->maxLength(500),
   321	                        ])
   322	                        ->visible(fn (MemberCard $record) => in_array($record->status, [CardStatus::ACTIVE->value, CardStatus::PRINTED->value, CardStatus::COLLECTED->value]))
   323	                        ->action(function (MemberCard $record, array $data) {
   324	                            app(CardService::class)->suspendCard($record, $data['reason']);
   325	                            Notification::make()->title('تم تعليق الكارنيه')->warning()->send();
   326	                        }),
   327	
   328	                    Tables\Actions\Action::make('reactivate_card')
   329	                        ->label('إعادة تفعيل')
   330	                        ->icon('heroicon-o-play-circle')
   331	                        ->color('success')
   332	                        ->requiresConfirmation()
   333	                        ->visible(fn (MemberCard $record) => $record->status === CardStatus::SUSPENDED->value)
   334	                        ->action(function (MemberCard $record) {
   335	                            app(CardService::class)->reactivateCard($record);
   336	                            Notification::make()->title('تم إعادة تفعيل الكارنيه')->success()->send();
   337	                        }),
   338	
   339	                    Tables\Actions\Action::make('cancel_card')
   340	                        ->label('إلغاء الكارنيه')
   341	                        ->icon('heroicon-o-x-circle')
   342	                        ->color('danger')
   343	                        ->requiresConfirmation()
   344	                        ->form([
   345	                            Forms\Components\Textarea::make('reason')
   346	                                ->label('سبب الإلغاء')
   347	                                ->required()
   348	                                ->maxLength(500),
   349	                        ])
   350	                        ->visible(fn (MemberCard $record) => !in_array($record->status, [CardStatus::CANCELLED->value, CardStatus::EXPIRED->value]))
   351	                        ->action(function (MemberCard $record, array $data) {
   352	                            app(CardService::class)->cancelCard($record, $data['reason']);
   353	                            Notification::make()->title('تم إلغاء الكارنيه')->danger()->send();
   354	                        }),
   355	                ]),
   356	            ])
   357	            ->bulkActions([
   358	                Tables\Actions\BulkActionGroup::make([
   359	                    Tables\Actions\BulkAction::make('bulk_print')
   360	                        ->label('تسجيل طباعة مجمعة')
   361	                        ->icon('heroicon-o-printer')
   362	                        ->color('success')
   363	                        ->requiresConfirmation()
   364	                        ->modalHeading('طباعة مجمعة')
   365	                        ->modalDescription('هل تريد تسجيل طباعة جميع الكروت المحددة؟')
   366	                        ->action(function (Collection $records) {
   367	                            $ids = $records->pluck('id')->toArray();
   368	                            $count = app(CardService::class)->bulkMarkAsPrinted($ids);
   369	                            Notification::make()->title("تم تسجيل طباعة {$count} كارنيه")->success()->send();
   370	                        })
   371	                        ->deselectRecordsAfterCompletion(),
   372	
   373	                    Tables\Actions\DeleteBulkAction::make()->label('حذف المحدد'),
   374	                ]),
   375	            ])
   376	            ->defaultSort('created_at', 'desc')
   377	            ->poll('30s');
   378	    }
   379	
   380	    public static function infolist(Infolist $infolist): Infolist
   381	    {
   382	        return $infolist
   383	            ->schema([
   384	                Infolists\Components\Section::make('بيانات الكارنيه')
   385	                    ->icon('heroicon-o-identification')
   386	                    ->schema([
   387	                        Infolists\Components\TextEntry::make('card_number')
   388	                            ->label('رقم الكارنيه')
   389	                            ->weight('bold')
   390	                            ->size('lg')
   391	                            ->copyable(),
   392	
   393	                        Infolists\Components\TextEntry::make('member.full_name_ar')
   394	                            ->label('اسم العضو'),
   395	
   396	                        Infolists\Components\TextEntry::make('member.membership_number')
   397	                            ->label('رقم العضوية')
   398	                            ->copyable(),
   399	
   400	                        Infolists\Components\TextEntry::make('card_type')
   401	                            ->label('النوع')
   402	                            ->badge()
   403	                            ->formatStateUsing(fn (string $state) => match ($state) {
   404	                                'main' => 'أساسي',
   405	                                'replacement' => 'بدل فاقد',
   406	                                'renewal' => 'تجديد',
   407	                                default => $state,
   408	                            }),
   409	
   410	                        Infolists\Components\TextEntry::make('status')
   411	                            ->label('الحالة')
   412	                            ->badge()
   413	                            ->formatStateUsing(fn (string $state) => CardStatus::tryFrom($state)?->getLabel() ?? $state)
   414	                            ->color(fn (string $state) => CardStatus::tryFrom($state)?->getColor() ?? 'gray'),
   415	
   416	                        Infolists\Components\TextEntry::make('issue_date')
   417	                            ->label('تاريخ الإصدار')
   418	                            ->date('Y-m-d'),
   419	
   420	                        Infolists\Components\TextEntry::make('expiry_date')
   421	                            ->label('تاريخ الانتهاء')
   422	                            ->date('Y-m-d'),
   423	
   424	                        Infolists\Components\ImageEntry::make('photo_path')
   425	                            ->label('الصورة')
   426	                            ->disk('public')
   427	                            ->circular()
   428	                            ->size(80),
   429	                    ])
   430	                    ->columns(3),
   431	
   432	                Infolists\Components\Section::make('بيانات الطباعة والاستلام')
   433	                    ->icon('heroicon-o-printer')
   434	                    ->schema([
   435	                        Infolists\Components\TextEntry::make('printed_at')
   436	                            ->label('تاريخ الطباعة')
   437	                            ->dateTime('Y-m-d H:i')
   438	                            ->placeholder('لم تتم الطباعة بعد'),
   439	
   440	                        Infolists\Components\TextEntry::make('printedBy.name')
   441	                            ->label('طبع بواسطة')
   442	                            ->placeholder('-'),
   443	
   444	                        Infolists\Components\TextEntry::make('collected_at')
   445	                            ->label('تاريخ الاستلام')
   446	                            ->dateTime('Y-m-d H:i')
   447	                            ->placeholder('لم يتم الاستلام بعد'),
   448	
   449	                        Infolists\Components\TextEntry::make('collectedBy.name')
   450	                            ->label('سلّم بواسطة')
   451	                            ->placeholder('-'),
   452	
   453	                        Infolists\Components\TextEntry::make('printing_cost')
   454	                            ->label('تكلفة الطباعة')
   455	                            ->money('EGP'),
   456	                    ])
   457	                    ->columns(3),
   458	
   459	                Infolists\Components\Section::make('بيانات إضافية')
   460	                    ->schema([
   461	                        Infolists\Components\TextEntry::make('barcode_data')
   462	                            ->label('بيانات الباركود')
   463	                            ->copyable(),
   464	
   465	                        Infolists\Components\TextEntry::make('replacement_reason')
   466	                            ->label('سبب الاستبدال')
   467	                            ->placeholder('-')
   468	                            ->visible(fn (MemberCard $record) => $record->card_type === 'replacement'),
   469	
   470	                        Infolists\Components\TextEntry::make('previousCard.card_number')
   471	                            ->label('الكارنيه السابق')
   472	                            ->placeholder('-')
   473	                            ->visible(fn (MemberCard $record) => !is_null($record->previous_card_id)),
   474	
   475	                        Infolists\Components\TextEntry::make('notes')
   476	                            ->label('ملاحظات')
   477	                            ->placeholder('لا توجد ملاحظات')
   478	                            ->columnSpanFull(),
   479	                    ])
   480	                    ->columns(2)
   481	                    ->collapsible(),
   482	            ]);
   483	    }
   484	
   485	    public static function getRelations(): array
   486	    {
   487	        return [];
   488	    }
   489	
   490	    public static function getPages(): array
   491	    {
   492	        return [
   493	            'index' => Pages\ListMemberCards::route('/'),
   494	            'create' => Pages\CreateMemberCard::route('/create'),
   495	            'view' => Pages\ViewMemberCard::route('/{record}'),
   496	            'edit' => Pages\EditMemberCard::route('/{record}/edit'),
   497	        ];
   498	    }
   499	
   500	    public static function getEloquentQuery(): Builder
   501	    {
   502	        return parent::getEloquentQuery()
   503	            ->where('is_archived', false)
   504	            ->with(['member', 'member.membershipType']);
   505	    }
   506	
   507	    public static function getGloballySearchableAttributes(): array
   508	    {
   509	        return ['card_number', 'member.full_name_ar', 'member.membership_number', 'barcode_data'];
   510	    }
   511	
   512	    public static function getGlobalSearchResultDetails(\Illuminate\Database\Eloquent\Model $record): array
   513	    {
   514	        return [
   515	            'العضو' => $record->member?->full_name_ar ?? '',
   516	            'الحالة' => CardStatus::tryFrom($record->status)?->getLabel() ?? $record->status,
   517	        ];
   518	    }
   519	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [123]: app/Filament/Resources/MemberCardResource.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [124/494]: app/Filament/Resources/MemberCardResource/Pages/CreateMemberCard.php
│ LANGUAGE: php | LINES: 76 | SIZE: 2490 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Filament\Resources\MemberCardResource\Pages;
     4	
     5	use App\Filament\Resources\MemberCardResource;
     6	use App\Models\Member;
     7	use App\Services\Cards\CardService;
     8	use Filament\Notifications\Notification;
     9	use Filament\Resources\Pages\CreateRecord;
    10	
    11	class CreateMemberCard extends CreateRecord
    12	{
    13	    protected static string $resource = MemberCardResource::class;
    14	
    15	    protected function getRedirectUrl(): string
    16	    {
    17	        return $this->getResource()::getUrl('view', ['record' => $this->record]);
    18	    }
    19	
    20	    protected function mutateFormDataBeforeCreate(array $data): array
    21	    {
    22	        // Check eligibility before creating
    23	        $member = Member::find($data['member_id']);
    24	        if ($member && $data['card_type'] === 'main') {
    25	            $cardService = app(CardService::class);
    26	            $eligibility = $cardService->canPrintCard($member);
    27	
    28	            if (!$eligibility['eligible']) {
    29	                // We still allow creation, but notify about issues
    30	                foreach ($eligibility['errors'] as $error) {
    31	                    Notification::make()
    32	                        ->title('تنبيه')
    33	                        ->body($error)
    34	                        ->warning()
    35	                        ->persistent()
    36	                        ->send();
    37	                }
    38	            }
    39	        }
    40	
    41	        return $data;
    42	    }
    43	
    44	    protected function afterCreate(): void
    45	    {
    46	        // The card number and barcode are generated by CardService
    47	        // But since we're using Filament's create form, we issue through the service
    48	        $cardService = app(CardService::class);
    49	        $member = $this->record->member;
    50	
    51	        if ($member) {
    52	            // Update the record with generated card number if not already set
    53	            if (empty($this->record->card_number)) {
    54	                $card = $cardService->issueCard(
    55	                    $member,
    56	                    $this->record->card_type,
    57	                    $this->record->notes,
    58	                    $this->record->previous_card_id,
    59	                    $this->record->replacement_reason
    60	                );
    61	
    62	                // Redirect to the properly created card
    63	                $this->record = $card;
    64	            }
    65	        }
    66	
    67	        Notification::make()
    68	            ->title('تم إصدار الكارنيه بنجاح')
    69	            ->success()
    70	            ->send();
    71	    }
    72	
    73	    protected function getCreatedNotificationTitle(): ?string
    74	    {
    75	        return null; // We handle notification in afterCreate
    76	    }
    77	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [124]: app/Filament/Resources/MemberCardResource/Pages/CreateMemberCard.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [125/494]: app/Filament/Resources/MemberCardResource/Pages/EditMemberCard.php
│ LANGUAGE: php | LINES: 24 | SIZE: 637 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Filament\Resources\MemberCardResource\Pages;
     4	
     5	use App\Filament\Resources\MemberCardResource;
     6	use Filament\Actions;
     7	use Filament\Resources\Pages\EditRecord;
     8	
     9	class EditMemberCard extends EditRecord
    10	{
    11	    protected static string $resource = MemberCardResource::class;
    12	
    13	    protected function getHeaderActions(): array
    14	    {
    15	        return [
    16	            Actions\ViewAction::make()->label('عرض'),
    17	            Actions\DeleteAction::make()->label('حذف'),
    18	        ];
    19	    }
    20	
    21	    protected function getRedirectUrl(): string
    22	    {
    23	        return $this->getResource()::getUrl('view', ['record' => $this->record]);
    24	    }
    25	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [125]: app/Filament/Resources/MemberCardResource/Pages/EditMemberCard.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [126/494]: app/Filament/Resources/MemberCardResource/Pages/ListMemberCards.php
│ LANGUAGE: php | LINES: 28 | SIZE: 700 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Filament\Resources\MemberCardResource\Pages;
     4	
     5	use App\Filament\Resources\MemberCardResource;
     6	use App\Filament\Widgets\CardStatsWidget;
     7	use Filament\Actions;
     8	use Filament\Resources\Pages\ListRecords;
     9	
    10	class ListMemberCards extends ListRecords
    11	{
    12	    protected static string $resource = MemberCardResource::class;
    13	
    14	    protected function getHeaderActions(): array
    15	    {
    16	        return [
    17	            Actions\CreateAction::make()
    18	                ->label('إصدار كارنيه جديد')
    19	                ->icon('heroicon-o-plus-circle'),
    20	        ];
    21	    }
    22	
    23	    protected function getHeaderWidgets(): array
    24	    {
    25	        return [
    26	            CardStatsWidget::class,
    27	        ];
    28	    }
    29	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [126]: app/Filament/Resources/MemberCardResource/Pages/ListMemberCards.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [127/494]: app/Filament/Resources/MemberCardResource/Pages/ViewMemberCard.php
│ LANGUAGE: php | LINES: 54 | SIZE: 2345 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Filament\Resources\MemberCardResource\Pages;
     4	
     5	use App\Enums\CardStatus;
     6	use App\Filament\Resources\MemberCardResource;
     7	use App\Services\Cards\CardService;
     8	use Filament\Actions;
     9	use Filament\Notifications\Notification;
    10	use Filament\Resources\Pages\ViewRecord;
    11	
    12	class ViewMemberCard extends ViewRecord
    13	{
    14	    protected static string $resource = MemberCardResource::class;
    15	
    16	    protected function getHeaderActions(): array
    17	    {
    18	        return [
    19	            Actions\EditAction::make()->label('تعديل'),
    20	
    21	            Actions\Action::make('print_preview')
    22	                ->label('معاينة الطباعة')
    23	                ->icon('heroicon-o-printer')
    24	                ->color('info')
    25	                ->url(fn () => route('filament.admin.pages.card-preview', ['card' => $this->record->id]))
    26	                ->openUrlInNewTab(),
    27	
    28	            Actions\Action::make('mark_printed')
    29	                ->label('تسجيل الطباعة')
    30	                ->icon('heroicon-o-printer')
    31	                ->color('success')
    32	                ->requiresConfirmation()
    33	                ->visible(fn () => $this->record->status === CardStatus::ACTIVE->value && is_null($this->record->printed_at))
    34	                ->action(function () {
    35	                    app(CardService::class)->markAsPrinted($this->record);
    36	                    Notification::make()->title('تم تسجيل الطباعة بنجاح')->success()->send();
    37	                    $this->refreshFormData(['status', 'printed_at', 'printed_by']);
    38	                }),
    39	
    40	            Actions\Action::make('mark_collected')
    41	                ->label('تسجيل الاستلام')
    42	                ->icon('heroicon-o-hand-raised')
    43	                ->color('success')
    44	                ->requiresConfirmation()
    45	                ->visible(fn () => in_array($this->record->status, [CardStatus::PRINTED->value, CardStatus::ACTIVE->value]) && is_null($this->record->collected_at))
    46	                ->action(function () {
    47	                    app(CardService::class)->markAsCollected($this->record);
    48	                    Notification::make()->title('تم تسجيل الاستلام بنجاح')->success()->send();
    49	                    $this->refreshFormData(['status', 'collected_at', 'collected_by']);
    50	                }),
    51	
    52	            Actions\DeleteAction::make()->label('حذف'),
    53	        ];
    54	    }
    55	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [127]: app/Filament/Resources/MemberCardResource/Pages/ViewMemberCard.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [128/494]: app/Filament/Resources/MemberDocumentResource.php
│ LANGUAGE: php | LINES: 450 | SIZE: 20963 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Filament\Resources;
     4	
     5	use App\Filament\Resources\MemberDocumentResource\Pages;
     6	use App\Models\Member;
     7	use App\Models\MemberDocument;
     8	use App\Services\Documents\DocumentService;
     9	use Filament\Forms;
    10	use Filament\Forms\Form;
    11	use Filament\Infolists;
    12	use Filament\Infolists\Infolist;
    13	use Filament\Notifications\Notification;
    14	use Filament\Resources\Resource;
    15	use Filament\Tables;
    16	use Filament\Tables\Table;
    17	use Illuminate\Database\Eloquent\Builder;
    18	use Illuminate\Database\Eloquent\Collection;
    19	
    20	class MemberDocumentResource extends Resource
    21	{
    22	    protected static ?string $model = MemberDocument::class;
    23	
    24	    protected static ?string $navigationIcon = 'heroicon-o-document-duplicate';
    25	
    26	    protected static ?string $navigationGroup = 'البطاقات والمستندات';
    27	
    28	    protected static ?int $navigationSort = 2;
    29	
    30	    protected static ?string $modelLabel = 'مستند';
    31	
    32	    protected static ?string $pluralModelLabel = 'مستندات الأعضاء';
    33	
    34	    protected static ?string $navigationLabel = 'مستندات الأعضاء';
    35	
    36	    protected static ?string $slug = 'member-documents';
    37	
    38	    protected static ?string $recordTitleAttribute = 'original_filename';
    39	
    40	    public static function getNavigationBadge(): ?string
    41	    {
    42	        return static::getModel()::where('is_archived', false)
    43	            ->where('is_verified', false)
    44	            ->count() ?: null;
    45	    }
    46	
    47	    public static function getNavigationBadgeColor(): string|array|null
    48	    {
    49	        return 'warning';
    50	    }
    51	
    52	    public static function form(Form $form): Form
    53	    {
    54	        return $form
    55	            ->schema([
    56	                Forms\Components\Section::make('بيانات المستند')
    57	                    ->icon('heroicon-o-document-text')
    58	                    ->schema([
    59	                        Forms\Components\Select::make('member_id')
    60	                            ->label('العضو')
    61	                            ->relationship('member', 'full_name_ar', fn (Builder $query) => $query->where('is_archived', false))
    62	                            ->getOptionLabelFromRecordUsing(fn (Member $record) => "{$record->membership_number} - {$record->full_name_ar}")
    63	                            ->searchable(['full_name_ar', 'full_name_en', 'membership_number', 'national_id'])
    64	                            ->preload()
    65	                            ->required()
    66	                            ->live()
    67	                            ->columnSpanFull(),
    68	
    69	                        Forms\Components\Select::make('dependent_id')
    70	                            ->label('التابع (اختياري)')
    71	                            ->options(function (Forms\Get $get) {
    72	                                $memberId = $get('member_id');
    73	                                if (!$memberId) {
    74	                                    return [];
    75	                                }
    76	                                return \App\Models\MemberDependent::where('member_id', $memberId)
    77	                                    ->where('is_archived', false)
    78	                                    ->pluck('full_name_ar', 'id')
    79	                                    ->toArray();
    80	                            })
    81	                            ->searchable()
    82	                            ->placeholder('للعضو نفسه')
    83	                            ->helperText('اتركه فارغاً إذا كان المستند خاصاً بالعضو'),
    84	
    85	                        Forms\Components\Select::make('document_type_id')
    86	                            ->label('نوع المستند')
    87	                            ->relationship('documentType', 'name_ar', fn (Builder $query) => $query->where('is_active', true)->where('is_archived', false))
    88	                            ->required()
    89	                            ->searchable()
    90	                            ->preload()
    91	                            ->live(),
    92	
    93	                        Forms\Components\FileUpload::make('file_path')
    94	                            ->label('الملف')
    95	                            ->disk('public')
    96	                            ->directory(fn (Forms\Get $get) => 'documents/members/' . ($get('member_id') ?? 'temp'))
    97	                            ->required()
    98	                            ->maxSize(10240) // 10MB max
    99	                            ->acceptedFileTypes([
   100	                                'application/pdf',
   101	                                'image/jpeg',
   102	                                'image/png',
   103	                                'image/gif',
   104	                                'image/webp',
   105	                                'application/msword',
   106	                                'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
   107	                            ])
   108	                            ->openable()
   109	                            ->downloadable()
   110	                            ->previewable()
   111	                            ->columnSpanFull(),
   112	
   113	                        Forms\Components\DatePicker::make('expiry_date')
   114	                            ->label('تاريخ انتهاء الصلاحية')
   115	                            ->helperText('اتركه فارغاً إذا لم يكن للمستند تاريخ انتهاء'),
   116	
   117	                        Forms\Components\Textarea::make('notes')
   118	                            ->label('ملاحظات')
   119	                            ->rows(2)
   120	                            ->maxLength(500)
   121	                            ->columnSpanFull(),
   122	                    ])
   123	                    ->columns(2),
   124	
   125	                Forms\Components\Section::make('التحقق')
   126	                    ->icon('heroicon-o-check-badge')
   127	                    ->schema([
   128	                        Forms\Components\Toggle::make('is_verified')
   129	                            ->label('تم التحقق')
   130	                            ->default(false)
   131	                            ->live(),
   132	
   133	                        Forms\Components\Textarea::make('verification_notes')
   134	                            ->label('ملاحظات التحقق')
   135	                            ->rows(2)
   136	                            ->maxLength(500)
   137	                            ->visible(fn (Forms\Get $get) => $get('is_verified') !== null),
   138	                    ])
   139	                    ->visibleOn('edit')
   140	                    ->collapsible(),
   141	            ]);
   142	    }
   143	
   144	    public static function table(Table $table): Table
   145	    {
   146	        return $table
   147	            ->columns([
   148	                Tables\Columns\TextColumn::make('member.full_name_ar')
   149	                    ->label('العضو')
   150	                    ->searchable()
   151	                    ->sortable()
   152	                    ->description(fn (MemberDocument $record) => $record->member?->membership_number),
   153	
   154	                Tables\Columns\TextColumn::make('dependent.full_name_ar')
   155	                    ->label('التابع')
   156	                    ->placeholder('العضو نفسه')
   157	                    ->toggleable(),
   158	
   159	                Tables\Columns\TextColumn::make('documentType.name_ar')
   160	                    ->label('نوع المستند')
   161	                    ->searchable()
   162	                    ->sortable()
   163	                    ->badge()
   164	                    ->color('info'),
   165	
   166	                Tables\Columns\TextColumn::make('original_filename')
   167	                    ->label('اسم الملف')
   168	                    ->searchable()
   169	                    ->limit(30)
   170	                    ->tooltip(fn (MemberDocument $record) => $record->original_filename),
   171	
   172	                Tables\Columns\TextColumn::make('file_size_kb')
   173	                    ->label('الحجم')
   174	                    ->formatStateUsing(fn (?int $state) => $state ? ($state >= 1024 ? round($state / 1024, 1) . ' MB' : $state . ' KB') : '-')
   175	                    ->sortable(),
   176	
   177	                Tables\Columns\TextColumn::make('mime_type')
   178	                    ->label('النوع')
   179	                    ->badge()
   180	                    ->formatStateUsing(fn (?string $state) => match (true) {
   181	                        str_contains($state ?? '', 'pdf') => 'PDF',
   182	                        str_contains($state ?? '', 'image') => 'صورة',
   183	                        str_contains($state ?? '', 'word') => 'Word',
   184	                        default => $state ?? '-',
   185	                    })
   186	                    ->color(fn (?string $state) => match (true) {
   187	                        str_contains($state ?? '', 'pdf') => 'danger',
   188	                        str_contains($state ?? '', 'image') => 'success',
   189	                        str_contains($state ?? '', 'word') => 'info',
   190	                        default => 'gray',
   191	                    })
   192	                    ->toggleable(isToggledHiddenByDefault: true),
   193	
   194	                Tables\Columns\IconColumn::make('is_verified')
   195	                    ->label('التحقق')
   196	                    ->boolean()
   197	                    ->trueIcon('heroicon-o-check-badge')
   198	                    ->falseIcon('heroicon-o-clock')
   199	                    ->trueColor('success')
   200	                    ->falseColor('warning'),
   201	
   202	                Tables\Columns\TextColumn::make('verifiedBy.name')
   203	                    ->label('تحقق بواسطة')
   204	                    ->placeholder('-')
   205	                    ->toggleable(isToggledHiddenByDefault: true),
   206	
   207	                Tables\Columns\TextColumn::make('verified_at')
   208	                    ->label('تاريخ التحقق')
   209	                    ->dateTime('Y-m-d H:i')
   210	                    ->placeholder('-')
   211	                    ->toggleable(isToggledHiddenByDefault: true),
   212	
   213	                Tables\Columns\TextColumn::make('expiry_date')
   214	                    ->label('تاريخ الانتهاء')
   215	                    ->date('Y-m-d')
   216	                    ->placeholder('غير محدد')
   217	                    ->color(fn (MemberDocument $record) => $record->expiry_date && $record->expiry_date < now()->toDateString() ? 'danger' : null),
   218	
   219	                Tables\Columns\TextColumn::make('created_at')
   220	                    ->label('تاريخ الرفع')
   221	                    ->dateTime('Y-m-d H:i')
   222	                    ->sortable(),
   223	            ])
   224	            ->filters([
   225	                Tables\Filters\SelectFilter::make('document_type_id')
   226	                    ->label('نوع المستند')
   227	                    ->relationship('documentType', 'name_ar')
   228	                    ->searchable()
   229	                    ->preload()
   230	                    ->multiple(),
   231	
   232	                Tables\Filters\TernaryFilter::make('is_verified')
   233	                    ->label('حالة التحقق')
   234	                    ->trueLabel('تم التحقق')
   235	                    ->falseLabel('في الانتظار')
   236	                    ->placeholder('الكل'),
   237	
   238	                Tables\Filters\Filter::make('has_dependent')
   239	                    ->label('مستندات التوابع')
   240	                    ->query(fn (Builder $query) => $query->whereNotNull('dependent_id'))
   241	                    ->toggle(),
   242	
   243	                Tables\Filters\Filter::make('expired')
   244	                    ->label('منتهي الصلاحية')
   245	                    ->query(fn (Builder $query) => $query->whereNotNull('expiry_date')->where('expiry_date', '<', now()->toDateString()))
   246	                    ->toggle(),
   247	
   248	                Tables\Filters\Filter::make('expiring_soon')
   249	                    ->label('ينتهي قريباً (30 يوم)')
   250	                    ->query(fn (Builder $query) => $query->whereNotNull('expiry_date')
   251	                        ->whereBetween('expiry_date', [now()->toDateString(), now()->addDays(30)->toDateString()]))
   252	                    ->toggle(),
   253	
   254	                Tables\Filters\SelectFilter::make('member_id')
   255	                    ->label('العضو')
   256	                    ->relationship('member', 'full_name_ar')
   257	                    ->searchable()
   258	                    ->preload(),
   259	            ])
   260	            ->actions([
   261	                Tables\Actions\ActionGroup::make([
   262	                    Tables\Actions\ViewAction::make()->label('عرض'),
   263	                    Tables\Actions\EditAction::make()->label('تعديل'),
   264	
   265	                    Tables\Actions\Action::make('download')
   266	                        ->label('تحميل')
   267	                        ->icon('heroicon-o-arrow-down-tray')
   268	                        ->color('info')
   269	                        ->action(function (MemberDocument $record) {
   270	                            if ($record->file_path && \Storage::disk('public')->exists($record->file_path)) {
   271	                                return response()->download(
   272	                                    \Storage::disk('public')->path($record->file_path),
   273	                                    $record->original_filename
   274	                                );
   275	                            }
   276	                            Notification::make()->title('الملف غير موجود')->danger()->send();
   277	                        }),
   278	
   279	                    Tables\Actions\Action::make('verify')
   280	                        ->label('تأكيد التحقق')
   281	                        ->icon('heroicon-o-check-badge')
   282	                        ->color('success')
   283	                        ->requiresConfirmation()
   284	                        ->form([
   285	                            Forms\Components\Textarea::make('verification_notes')
   286	                                ->label('ملاحظات التحقق')
   287	                                ->maxLength(500),
   288	                        ])
   289	                        ->visible(fn (MemberDocument $record) => !$record->is_verified)
   290	                        ->action(function (MemberDocument $record, array $data) {
   291	                            app(DocumentService::class)->verifyDocument($record, $data['verification_notes'] ?? null);
   292	                            Notification::make()->title('تم التحقق من المستند')->success()->send();
   293	                        }),
   294	
   295	                    Tables\Actions\Action::make('reject')
   296	                        ->label('رفض المستند')
   297	                        ->icon('heroicon-o-x-circle')
   298	                        ->color('danger')
   299	                        ->requiresConfirmation()
   300	                        ->form([
   301	                            Forms\Components\Textarea::make('reason')
   302	                                ->label('سبب الرفض')
   303	                                ->required()
   304	                                ->maxLength(500),
   305	                        ])
   306	                        ->visible(fn (MemberDocument $record) => !$record->is_verified || ($record->is_verified && !str_contains($record->verification_notes ?? '', 'مرفوض')))
   307	                        ->action(function (MemberDocument $record, array $data) {
   308	                            app(DocumentService::class)->rejectDocument($record, $data['reason']);
   309	                            Notification::make()->title('تم رفض المستند')->warning()->send();
   310	                        }),
   311	
   312	                    Tables\Actions\Action::make('archive')
   313	                        ->label('أرشفة')
   314	                        ->icon('heroicon-o-archive-box')
   315	                        ->color('gray')
   316	                        ->requiresConfirmation()
   317	                        ->action(function (MemberDocument $record) {
   318	                            app(DocumentService::class)->archiveDocument($record);
   319	                            Notification::make()->title('تم أرشفة المستند')->success()->send();
   320	                        }),
   321	                ]),
   322	            ])
   323	            ->bulkActions([
   324	                Tables\Actions\BulkActionGroup::make([
   325	                    Tables\Actions\BulkAction::make('bulk_verify')
   326	                        ->label('تحقق مجمع')
   327	                        ->icon('heroicon-o-check-badge')
   328	                        ->color('success')
   329	                        ->requiresConfirmation()
   330	                        ->action(function (Collection $records) {
   331	                            $documentService = app(DocumentService::class);
   332	                            $count = 0;
   333	                            foreach ($records as $record) {
   334	                                if (!$record->is_verified) {
   335	                                    $documentService->verifyDocument($record, 'تحقق مجمع');
   336	                                    $count++;
   337	                                }
   338	                            }
   339	                            Notification::make()->title("تم التحقق من {$count} مستند")->success()->send();
   340	                        })
   341	                        ->deselectRecordsAfterCompletion(),
   342	
   343	                    Tables\Actions\DeleteBulkAction::make()->label('حذف المحدد'),
   344	                ]),
   345	            ])
   346	            ->defaultSort('created_at', 'desc');
   347	    }
   348	
   349	    public static function infolist(Infolist $infolist): Infolist
   350	    {
   351	        return $infolist
   352	            ->schema([
   353	                Infolists\Components\Section::make('بيانات المستند')
   354	                    ->icon('heroicon-o-document-text')
   355	                    ->schema([
   356	                        Infolists\Components\TextEntry::make('member.full_name_ar')
   357	                            ->label('العضو'),
   358	
   359	                        Infolists\Components\TextEntry::make('member.membership_number')
   360	                            ->label('رقم العضوية')
   361	                            ->copyable(),
   362	
   363	                        Infolists\Components\TextEntry::make('dependent.full_name_ar')
   364	                            ->label('التابع')
   365	                            ->placeholder('العضو نفسه'),
   366	
   367	                        Infolists\Components\TextEntry::make('documentType.name_ar')
   368	                            ->label('نوع المستند')
   369	                            ->badge()
   370	                            ->color('info'),
   371	
   372	                        Infolists\Components\TextEntry::make('original_filename')
   373	                            ->label('اسم الملف الأصلي'),
   374	
   375	                        Infolists\Components\TextEntry::make('mime_type')
   376	                            ->label('نوع الملف'),
   377	
   378	                        Infolists\Components\TextEntry::make('file_size_kb')
   379	                            ->label('حجم الملف')
   380	                            ->formatStateUsing(fn (?int $state) => $state ? ($state >= 1024 ? round($state / 1024, 1) . ' MB' : $state . ' KB') : '-'),
   381	
   382	                        Infolists\Components\TextEntry::make('expiry_date')
   383	                            ->label('تاريخ الانتهاء')
   384	                            ->date('Y-m-d')
   385	                            ->placeholder('غير محدد'),
   386	                    ])
   387	                    ->columns(3),
   388	
   389	                Infolists\Components\Section::make('حالة التحقق')
   390	                    ->icon('heroicon-o-check-badge')
   391	                    ->schema([
   392	                        Infolists\Components\IconEntry::make('is_verified')
   393	                            ->label('تم التحقق')
   394	                            ->boolean()
   395	                            ->trueIcon('heroicon-o-check-badge')
   396	                            ->falseIcon('heroicon-o-clock'),
   397	
   398	                        Infolists\Components\TextEntry::make('verifiedBy.name')
   399	                            ->label('تحقق بواسطة')
   400	                            ->placeholder('-'),
   401	
   402	                        Infolists\Components\TextEntry::make('verified_at')
   403	                            ->label('تاريخ التحقق')
   404	                            ->dateTime('Y-m-d H:i')
   405	                            ->placeholder('-'),
   406	
   407	                        Infolists\Components\TextEntry::make('verification_notes')
   408	                            ->label('ملاحظات التحقق')
   409	                            ->placeholder('-')
   410	                            ->columnSpanFull(),
   411	                    ])
   412	                    ->columns(3),
   413	
   414	                Infolists\Components\Section::make('ملاحظات')
   415	                    ->schema([
   416	                        Infolists\Components\TextEntry::make('notes')
   417	                            ->label('ملاحظات')
   418	                            ->placeholder('لا توجد ملاحظات')
   419	                            ->columnSpanFull(),
   420	                    ])
   421	                    ->collapsible(),
   422	            ]);
   423	    }
   424	
   425	    public static function getRelations(): array
   426	    {
   427	        return [];
   428	    }
   429	
   430	    public static function getPages(): array
   431	    {
   432	        return [
   433	            'index' => Pages\ListMemberDocuments::route('/'),
   434	            'create' => Pages\CreateMemberDocument::route('/create'),
   435	            'view' => Pages\ViewMemberDocument::route('/{record}'),
   436	            'edit' => Pages\EditMemberDocument::route('/{record}/edit'),
   437	        ];
   438	    }
   439	
   440	    public static function getEloquentQuery(): Builder
   441	    {
   442	        return parent::getEloquentQuery()
   443	            ->where('is_archived', false)
   444	            ->with(['member', 'documentType', 'dependent']);
   445	    }
   446	
   447	    public static function getGloballySearchableAttributes(): array
   448	    {
   449	        return ['original_filename', 'member.full_name_ar', 'member.membership_number'];
   450	    }
   451	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [128]: app/Filament/Resources/MemberDocumentResource.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [129/494]: app/Filament/Resources/MemberDocumentResource/Pages/CreateMemberDocument.php
│ LANGUAGE: php | LINES: 38 | SIZE: 1194 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Filament\Resources\MemberDocumentResource\Pages;
     4	
     5	use App\Filament\Resources\MemberDocumentResource;
     6	use Filament\Resources\Pages\CreateRecord;
     7	use Illuminate\Support\Facades\Auth;
     8	
     9	class CreateMemberDocument extends CreateRecord
    10	{
    11	    protected static string $resource = MemberDocumentResource::class;
    12	
    13	    protected function mutateFormDataBeforeCreate(array $data): array
    14	    {
    15	        $data['created_by'] = Auth::id();
    16	        $data['updated_by'] = Auth::id();
    17	
    18	        // Calculate file size if uploaded
    19	        if (isset($data['file_path']) && is_string($data['file_path'])) {
    20	            $fullPath = storage_path('app/public/' . $data['file_path']);
    21	            if (file_exists($fullPath)) {
    22	                $data['file_size_kb'] = (int) ceil(filesize($fullPath) / 1024);
    23	                $data['mime_type'] = mime_content_type($fullPath);
    24	            }
    25	        }
    26	
    27	        return $data;
    28	    }
    29	
    30	    protected function getRedirectUrl(): string
    31	    {
    32	        return $this->getResource()::getUrl('view', ['record' => $this->record]);
    33	    }
    34	
    35	    protected function getCreatedNotificationTitle(): ?string
    36	    {
    37	        return 'تم رفع المستند بنجاح';
    38	    }
    39	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [129]: app/Filament/Resources/MemberDocumentResource/Pages/CreateMemberDocument.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [130/494]: app/Filament/Resources/MemberDocumentResource/Pages/EditMemberDocument.php
│ LANGUAGE: php | LINES: 43 | SIZE: 1202 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Filament\Resources\MemberDocumentResource\Pages;
     4	
     5	use App\Filament\Resources\MemberDocumentResource;
     6	use Filament\Actions;
     7	use Filament\Resources\Pages\EditRecord;
     8	use Illuminate\Support\Facades\Auth;
     9	
    10	class EditMemberDocument extends EditRecord
    11	{
    12	    protected static string $resource = MemberDocumentResource::class;
    13	
    14	    protected function mutateFormDataBeforeSave(array $data): array
    15	    {
    16	        $data['updated_by'] = Auth::id();
    17	
    18	        // If verification changed
    19	        if (isset($data['is_verified']) && $data['is_verified'] && !$this->record->is_verified) {
    20	            $data['verified_by'] = Auth::id();
    21	            $data['verified_at'] = now();
    22	        }
    23	
    24	        return $data;
    25	    }
    26	
    27	    protected function getHeaderActions(): array
    28	    {
    29	        return [
    30	            Actions\ViewAction::make()->label('عرض'),
    31	            Actions\DeleteAction::make()->label('حذف'),
    32	        ];
    33	    }
    34	
    35	    protected function getRedirectUrl(): string
    36	    {
    37	        return $this->getResource()::getUrl('view', ['record' => $this->record]);
    38	    }
    39	
    40	    protected function getSavedNotificationTitle(): ?string
    41	    {
    42	        return 'تم تحديث المستند بنجاح';
    43	    }
    44	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [130]: app/Filament/Resources/MemberDocumentResource/Pages/EditMemberDocument.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [131/494]: app/Filament/Resources/MemberDocumentResource/Pages/ListMemberDocuments.php
│ LANGUAGE: php | LINES: 28 | SIZE: 730 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Filament\Resources\MemberDocumentResource\Pages;
     4	
     5	use App\Filament\Resources\MemberDocumentResource;
     6	use App\Filament\Widgets\DocumentComplianceWidget;
     7	use Filament\Actions;
     8	use Filament\Resources\Pages\ListRecords;
     9	
    10	class ListMemberDocuments extends ListRecords
    11	{
    12	    protected static string $resource = MemberDocumentResource::class;
    13	
    14	    protected function getHeaderActions(): array
    15	    {
    16	        return [
    17	            Actions\CreateAction::make()
    18	                ->label('رفع مستند جديد')
    19	                ->icon('heroicon-o-arrow-up-tray'),
    20	        ];
    21	    }
    22	
    23	    protected function getHeaderWidgets(): array
    24	    {
    25	        return [
    26	            DocumentComplianceWidget::class,
    27	        ];
    28	    }
    29	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [131]: app/Filament/Resources/MemberDocumentResource/Pages/ListMemberDocuments.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [132/494]: app/Filament/Resources/MemberDocumentResource/Pages/ViewMemberDocument.php
│ LANGUAGE: php | LINES: 48 | SIZE: 1924 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Filament\Resources\MemberDocumentResource\Pages;
     4	
     5	use App\Filament\Resources\MemberDocumentResource;
     6	use App\Services\Documents\DocumentService;
     7	use Filament\Actions;
     8	use Filament\Notifications\Notification;
     9	use Filament\Resources\Pages\ViewRecord;
    10	
    11	class ViewMemberDocument extends ViewRecord
    12	{
    13	    protected static string $resource = MemberDocumentResource::class;
    14	
    15	    protected function getHeaderActions(): array
    16	    {
    17	        return [
    18	            Actions\EditAction::make()->label('تعديل'),
    19	
    20	            Actions\Action::make('download')
    21	                ->label('تحميل الملف')
    22	                ->icon('heroicon-o-arrow-down-tray')
    23	                ->color('info')
    24	                ->action(function () {
    25	                    if ($this->record->file_path && \Storage::disk('public')->exists($this->record->file_path)) {
    26	                        return response()->download(
    27	                            \Storage::disk('public')->path($this->record->file_path),
    28	                            $this->record->original_filename
    29	                        );
    30	                    }
    31	                    Notification::make()->title('الملف غير موجود')->danger()->send();
    32	                }),
    33	
    34	            Actions\Action::make('verify')
    35	                ->label('تأكيد التحقق')
    36	                ->icon('heroicon-o-check-badge')
    37	                ->color('success')
    38	                ->requiresConfirmation()
    39	                ->visible(fn () => !$this->record->is_verified)
    40	                ->action(function () {
    41	                    app(DocumentService::class)->verifyDocument($this->record);
    42	                    Notification::make()->title('تم التحقق من المستند')->success()->send();
    43	                    $this->refreshFormData(['is_verified', 'verified_by', 'verified_at']);
    44	                }),
    45	
    46	            Actions\DeleteAction::make()->label('حذف'),
    47	        ];
    48	    }
    49	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [132]: app/Filament/Resources/MemberDocumentResource/Pages/ViewMemberDocument.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [133/494]: app/Filament/Resources/MemberResource.php
│ LANGUAGE: php | LINES: 981 | SIZE: 51261 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Filament\Resources;
     4	
     5	use App\Enums\Gender;
     6	use App\Enums\MembershipStatus;
     7	use App\Enums\MembershipType;
     8	use App\Enums\InterviewResult;
     9	use App\Enums\InterviewStatus;
    10	use App\Enums\BoardDecisionResult;
    11	use App\Enums\WorkflowProgressStatus;
    12	use App\Filament\Resources\MemberResource\Pages;
    13	use App\Filament\Resources\MemberResource\RelationManagers;
    14	use App\Models\Member;
    15	use App\Models\MembershipType as MembershipTypeModel;
    16	use App\Services\Membership\MembershipWorkflowService;
    17	use App\Services\Membership\MemberNumberService;
    18	use Filament\Forms;
    19	use Filament\Forms\Form;
    20	use Filament\Infolists;
    21	use Filament\Infolists\Infolist;
    22	use Filament\Resources\Resource;
    23	use Filament\Tables;
    24	use Filament\Tables\Table;
    25	use Filament\Notifications\Notification;
    26	use Filament\Support\Enums\FontWeight;
    27	use Illuminate\Database\Eloquent\Builder;
    28	use Illuminate\Database\Eloquent\SoftDeletingScope;
    29	use Illuminate\Database\Eloquent\Model;
    30	use Illuminate\Support\Carbon;
    31	
    32	class MemberResource extends Resource
    33	{
    34	    protected static ?string $model = Member::class;
    35	
    36	    protected static ?string $navigationIcon = 'heroicon-o-user-group';
    37	
    38	    protected static ?string $navigationGroup = 'إدارة العضويات';
    39	
    40	    protected static ?int $navigationSort = 1;
    41	
    42	    protected static ?string $modelLabel = 'عضو';
    43	
    44	    protected static ?string $pluralModelLabel = 'الأعضاء';
    45	
    46	    protected static ?string $recordTitleAttribute = 'full_name_ar';
    47	
    48	    protected static ?string $slug = 'members';
    49	
    50	    public static function getNavigationBadge(): ?string
    51	    {
    52	        return static::getModel()::where('membership_status', MembershipStatus::PENDING)->count() ?: null;
    53	    }
    54	
    55	    public static function getNavigationBadgeColor(): string|array|null
    56	    {
    57	        return 'warning';
    58	    }
    59	
    60	    public static function getGloballySearchableAttributes(): array
    61	    {
    62	        return [
    63	            'full_name_ar',
    64	            'full_name_en',
    65	            'membership_number',
    66	            'national_id',
    67	            'mobile',
    68	            'email',
    69	        ];
    70	    }
    71	
    72	    public static function getGlobalSearchResultDetails(Model $record): array
    73	    {
    74	        return [
    75	            'رقم العضوية' => $record->membership_number ?? 'لم يُصدر بعد',
    76	            'الحالة' => $record->membership_status?->getLabel(),
    77	            'الجوال' => $record->mobile,
    78	        ];
    79	    }
    80	
    81	    public static function form(Form $form): Form
    82	    {
    83	        return $form->schema([
    84	            Forms\Components\Tabs::make('member_tabs')
    85	                ->tabs([
    86	                    // === TAB 1: PERSONAL INFO ===
    87	                    Forms\Components\Tabs\Tab::make('البيانات الشخصية')
    88	                        ->icon('heroicon-o-user')
    89	                        ->schema([
    90	                            Forms\Components\Section::make('المعلومات الأساسية')
    91	                                ->description('بيانات العضو الرئيسية')
    92	                                ->icon('heroicon-o-identification')
    93	                                ->columns(3)
    94	                                ->schema([
    95	                                    Forms\Components\TextInput::make('full_name_ar')
    96	                                        ->label('الاسم الكامل (عربي)')
    97	                                        ->required()
    98	                                        ->maxLength(255)
    99	                                        ->placeholder('الاسم رباعي بالعربية')
   100	                                        ->columnSpan(1),
   101	
   102	                                    Forms\Components\TextInput::make('full_name_en')
   103	                                        ->label('الاسم الكامل (إنجليزي)')
   104	                                        ->maxLength(255)
   105	                                        ->placeholder('Full name in English')
   106	                                        ->columnSpan(1),
   107	
   108	                                    Forms\Components\TextInput::make('national_id')
   109	                                        ->label('الرقم القومي')
   110	                                        ->required()
   111	                                        ->unique(ignoreRecord: true)
   112	                                        ->maxLength(14)
   113	                                        ->minLength(14)
   114	                                        ->numeric()
   115	                                        ->placeholder('00000000000000')
   116	                                        ->columnSpan(1),
   117	
   118	                                    Forms\Components\Select::make('gender')
   119	                                        ->label('النوع')
   120	                                        ->options(Gender::toFilamentOptions())
   121	                                        ->required()
   122	                                        ->native(false)
   123	                                        ->columnSpan(1),
   124	
   125	                                    Forms\Components\DatePicker::make('date_of_birth')
   126	                                        ->label('تاريخ الميلاد')
   127	                                        ->required()
   128	                                        ->maxDate(now()->subYears(18))
   129	                                        ->displayFormat('Y-m-d')
   130	                                        ->native(false)
   131	                                        ->columnSpan(1),
   132	
   133	                                    Forms\Components\Select::make('blood_type')
   134	                                        ->label('فصيلة الدم')
   135	                                        ->options([
   136	                                            'A+' => 'A+',
   137	                                            'A-' => 'A-',
   138	                                            'B+' => 'B+',
   139	                                            'B-' => 'B-',
   140	                                            'AB+' => 'AB+',
   141	                                            'AB-' => 'AB-',
   142	                                            'O+' => 'O+',
   143	                                            'O-' => 'O-',
   144	                                        ])
   145	                                        ->native(false)
   146	                                        ->columnSpan(1),
   147	
   148	                                    Forms\Components\Select::make('religion')
   149	                                        ->label('الديانة')
   150	                                        ->options([
   151	                                            'muslim' => 'مسلم',
   152	                                            'christian' => 'مسيحي',
   153	                                            'other' => 'أخرى',
   154	                                        ])
   155	                                        ->native(false)
   156	                                        ->columnSpan(1),
   157	
   158	                                    Forms\Components\Select::make('marital_status')
   159	                                        ->label('الحالة الاجتماعية')
   160	                                        ->options([
   161	                                            'single' => 'أعزب',
   162	                                            'married' => 'متزوج',
   163	                                            'divorced' => 'مطلق',
   164	                                            'widowed' => 'أرمل',
   165	                                        ])
   166	                                        ->native(false)
   167	                                        ->columnSpan(1),
   168	
   169	                                    Forms\Components\TextInput::make('nationality')
   170	                                        ->label('الجنسية')
   171	                                        ->default('مصري')
   172	                                        ->maxLength(100)
   173	                                        ->columnSpan(1),
   174	                                ]),
   175	
   176	                            Forms\Components\Section::make('بيانات الاتصال')
   177	                                ->icon('heroicon-o-phone')
   178	                                ->columns(3)
   179	                                ->schema([
   180	                                    Forms\Components\TextInput::make('mobile')
   181	                                        ->label('رقم الجوال')
   182	                                        ->required()
   183	                                        ->tel()
   184	                                        ->maxLength(20)
   185	                                        ->placeholder('01XXXXXXXXX')
   186	                                        ->columnSpan(1),
   187	
   188	                                    Forms\Components\TextInput::make('phone')
   189	                                        ->label('رقم الهاتف')
   190	                                        ->tel()
   191	                                        ->maxLength(20)
   192	                                        ->columnSpan(1),
   193	
   194	                                    Forms\Components\TextInput::make('email')
   195	                                        ->label('البريد الإلكتروني')
   196	                                        ->email()
   197	                                        ->unique(ignoreRecord: true)
   198	                                        ->maxLength(255)
   199	                                        ->columnSpan(1),
   200	
   201	                                    Forms\Components\Textarea::make('address')
   202	                                        ->label('العنوان')
   203	                                        ->rows(2)
   204	                                        ->maxLength(500)
   205	                                        ->columnSpanFull(),
   206	                                ]),
   207	
   208	                            Forms\Components\Section::make('بيانات الطوارئ')
   209	                                ->icon('heroicon-o-exclamation-triangle')
   210	                                ->columns(3)
   211	                                ->collapsible()
   212	                                ->collapsed()
   213	                                ->schema([
   214	                                    Forms\Components\TextInput::make('emergency_contact_name')
   215	                                        ->label('اسم جهة اتصال الطوارئ')
   216	                                        ->maxLength(255)
   217	                                        ->columnSpan(1),
   218	
   219	                                    Forms\Components\TextInput::make('emergency_contact_phone')
   220	                                        ->label('هاتف الطوارئ')
   221	                                        ->tel()
   222	                                        ->maxLength(20)
   223	                                        ->columnSpan(1),
   224	
   225	                                    Forms\Components\TextInput::make('emergency_contact_relation')
   226	                                        ->label('صلة القرابة')
   227	                                        ->maxLength(100)
   228	                                        ->columnSpan(1),
   229	                                ]),
   230	                        ]),
   231	
   232	                    // === TAB 2: WORK INFO ===
   233	                    Forms\Components\Tabs\Tab::make('بيانات العمل')
   234	                        ->icon('heroicon-o-briefcase')
   235	                        ->schema([
   236	                            Forms\Components\Section::make('الوظيفة والعمل')
   237	                                ->columns(3)
   238	                                ->schema([
   239	                                    Forms\Components\TextInput::make('workplace')
   240	                                        ->label('جهة العمل')
   241	                                        ->maxLength(255)
   242	                                        ->columnSpan(1),
   243	
   244	                                    Forms\Components\TextInput::make('job_title')
   245	                                        ->label('المسمى الوظيفي')
   246	                                        ->maxLength(255)
   247	                                        ->columnSpan(1),
   248	
   249	                                    Forms\Components\TextInput::make('work_phone')
   250	                                        ->label('هاتف العمل')
   251	                                        ->tel()
   252	                                        ->maxLength(20)
   253	                                        ->columnSpan(1),
   254	
   255	                                    Forms\Components\Textarea::make('work_address')
   256	                                        ->label('عنوان العمل')
   257	                                        ->rows(2)
   258	                                        ->maxLength(500)
   259	                                        ->columnSpanFull(),
   260	                                ]),
   261	
   262	                            Forms\Components\Section::make('مُعرّفان (Referees)')
   263	                                ->description('عضوان حاليان يزكيان المتقدم')
   264	                                ->columns(2)
   265	                                ->schema([
   266	                                    Forms\Components\Select::make('referee_1_id')
   267	                                        ->label('المُعرّف الأول')
   268	                                        ->relationship('referee1', 'full_name_ar', fn(Builder $query) => $query->where('membership_status', MembershipStatus::ACTIVE))
   269	                                        ->searchable()
   270	                                        ->preload()
   271	                                        ->columnSpan(1),
   272	
   273	                                    Forms\Components\Select::make('referee_2_id')
   274	                                        ->label('المُعرّف الثاني')
   275	                                        ->relationship('referee2', 'full_name_ar', fn(Builder $query) => $query->where('membership_status', MembershipStatus::ACTIVE))
   276	                                        ->searchable()
   277	                                        ->preload()
   278	                                        ->columnSpan(1),
   279	                                ]),
   280	                        ]),
   281	
   282	                    // === TAB 3: MEMBERSHIP INFO ===
   283	                    Forms\Components\Tabs\Tab::make('بيانات العضوية')
   284	                        ->icon('heroicon-o-credit-card')
   285	                        ->schema([
   286	                            Forms\Components\Section::make('نوع العضوية والحالة')
   287	                                ->columns(3)
   288	                                ->schema([
   289	                                    Forms\Components\Select::make('membership_type_id')
   290	                                        ->label('نوع العضوية')
   291	                                        ->relationship('membershipType', 'name_ar')
   292	                                        ->required()
   293	                                        ->preload()
   294	                                        ->searchable()
   295	                                        ->native(false)
   296	                                        ->columnSpan(1),
   297	
   298	                                    Forms\Components\Select::make('membership_status')
   299	                                        ->label('حالة العضوية')
   300	                                        ->options(MembershipStatus::toFilamentOptions())
   301	                                        ->default(MembershipStatus::PENDING->value)
   302	                                        ->required()
   303	                                        ->native(false)
   304	                                        ->disabled(fn(string $operation): bool => $operation === 'create')
   305	                                        ->columnSpan(1),
   306	
   307	                                    Forms\Components\TextInput::make('membership_number')
   308	                                        ->label('رقم العضوية')
   309	                                        ->disabled()
   310	                                        ->placeholder('يُصدر تلقائياً عند السداد')
   311	                                        ->columnSpan(1),
   312	
   313	                                    Forms\Components\DatePicker::make('application_date')
   314	                                        ->label('تاريخ التقديم')
   315	                                        ->default(now())
   316	                                        ->required()
   317	                                        ->native(false)
   318	                                        ->columnSpan(1),
   319	
   320	                                    Forms\Components\DatePicker::make('approval_date')
   321	                                        ->label('تاريخ الموافقة')
   322	                                        ->native(false)
   323	                                        ->columnSpan(1),
   324	
   325	                                    Forms\Components\DatePicker::make('payment_date')
   326	                                        ->label('تاريخ السداد')
   327	                                        ->native(false)
   328	                                        ->columnSpan(1),
   329	                                ]),
   330	
   331	                            Forms\Components\Section::make('مرحلة سير العمل')
   332	                                ->columns(2)
   333	                                ->schema([
   334	                                    Forms\Components\Select::make('workflow_stage')
   335	                                        ->label('المرحلة الحالية')
   336	                                        ->options([
   337	                                            1 => 'المرحلة 1: تقديم الاستمارة',
   338	                                            2 => 'المرحلة 2: تجميع المستندات',
   339	                                            3 => 'المرحلة 3: سداد رسوم الاستمارة',
   340	                                            4 => 'المرحلة 4: مراجعة الطلب',
   341	                                            5 => 'المرحلة 5: المقابلة الشخصية',
   342	                                            6 => 'المرحلة 6: قرار مجلس الإدارة',
   343	                                            7 => 'المرحلة 7: سداد رسوم العضوية',
   344	                                            8 => 'المرحلة 8: إصدار الكارنيهات',
   345	                                        ])
   346	                                        ->default(1)
   347	                                        ->disabled(fn(string $operation): bool => $operation === 'create')
   348	                                        ->native(false)
   349	                                        ->columnSpan(1),
   350	
   351	                                    Forms\Components\Select::make('workflow_status')
   352	                                        ->label('حالة المرحلة')
   353	                                        ->options(WorkflowProgressStatus::toFilamentOptions())
   354	                                        ->default(WorkflowProgressStatus::PENDING->value)
   355	                                        ->disabled(fn(string $operation): bool => $operation === 'create')
   356	                                        ->native(false)
   357	                                        ->columnSpan(1),
   358	                                ]),
   359	
   360	                            Forms\Components\Section::make('ملاحظات خاصة')
   361	                                ->collapsible()
   362	                                ->collapsed()
   363	                                ->schema([
   364	                                    Forms\Components\RichEditor::make('notes')
   365	                                        ->label('ملاحظات')
   366	                                        ->toolbarButtons([
   367	                                            'bold',
   368	                                            'italic',
   369	                                            'underline',
   370	                                            'bulletList',
   371	                                            'orderedList',
   372	                                        ])
   373	                                        ->columnSpanFull(),
   374	
   375	                                    Forms\Components\KeyValue::make('metadata')
   376	                                        ->label('بيانات إضافية')
   377	                                        ->keyLabel('الحقل')
   378	                                        ->valueLabel('القيمة')
   379	                                        ->addActionLabel('إضافة حقل')
   380	                                        ->columnSpanFull(),
   381	                                ]),
   382	                        ]),
   383	
   384	                    // === TAB 4: PHOTO ===
   385	                    Forms\Components\Tabs\Tab::make('الصور والمرفقات')
   386	                        ->icon('heroicon-o-photo')
   387	                        ->schema([
   388	                            Forms\Components\Section::make('صورة العضو')
   389	                                ->schema([
   390	                                    Forms\Components\SpatieMediaLibraryFileUpload::make('photo')
   391	                                        ->label('الصورة الشخصية')
   392	                                        ->collection('member_photo')
   393	                                        ->image()
   394	                                        ->imageEditor()
   395	                                        ->imageEditorAspectRatios(['3:4'])
   396	                                        ->imageCropAspectRatio('3:4')
   397	                                        ->imageResizeTargetWidth('300')
   398	                                        ->imageResizeTargetHeight('400')
   399	                                        ->maxSize(2048)
   400	                                        ->acceptedFileTypes(['image/jpeg', 'image/png'])
   401	                                        ->helperText('صورة شخصية واضحة بخلفية بيضاء — 3:4 — حد أقصى 2 ميجابايت')
   402	                                        ->columnSpanFull(),
   403	                                ]),
   404	                        ]),
   405	                ])
   406	                ->persistTabInQueryString()
   407	                ->columnSpanFull(),
   408	        ]);
   409	    }
   410	
   411	    public static function table(Table $table): Table
   412	    {
   413	        return $table
   414	            ->columns([
   415	                Tables\Columns\SpatieMediaLibraryImageColumn::make('photo')
   416	                    ->collection('member_photo')
   417	                    ->label('')
   418	                    ->circular()
   419	                    ->size(40)
   420	                    ->defaultImageUrl(fn($record) => 'https://ui-avatars.com/api/?name=' . urlencode($record->full_name_ar) . '&background=0D8ABC&color=fff&size=40'),
   421	
   422	                Tables\Columns\TextColumn::make('membership_number')
   423	                    ->label('رقم العضوية')
   424	                    ->searchable()
   425	                    ->sortable()
   426	                    ->copyable()
   427	                    ->placeholder('—')
   428	                    ->weight(FontWeight::Bold)
   429	                    ->color('primary'),
   430	
   431	                Tables\Columns\TextColumn::make('full_name_ar')
   432	                    ->label('الاسم')
   433	                    ->searchable()
   434	                    ->sortable()
   435	                    ->weight(FontWeight::SemiBold)
   436	                    ->limit(30)
   437	                    ->tooltip(fn($record) => $record->full_name_ar),
   438	
   439	                Tables\Columns\TextColumn::make('national_id')
   440	                    ->label('الرقم القومي')
   441	                    ->searchable()
   442	                    ->toggleable()
   443	                    ->copyable()
   444	                    ->fontFamily('mono'),
   445	
   446	                Tables\Columns\TextColumn::make('mobile')
   447	                    ->label('الجوال')
   448	                    ->searchable()
   449	                    ->copyable()
   450	                    ->icon('heroicon-o-phone'),
   451	
   452	                Tables\Columns\TextColumn::make('membershipType.name_ar')
   453	                    ->label('نوع العضوية')
   454	                    ->badge()
   455	                    ->sortable(),
   456	
   457	                Tables\Columns\TextColumn::make('membership_status')
   458	                    ->label('الحالة')
   459	                    ->badge()
   460	                    ->color(fn(MembershipStatus $state): string => $state->getColor())
   461	                    ->icon(fn(MembershipStatus $state): string => $state->getIcon())
   462	                    ->sortable(),
   463	
   464	                Tables\Columns\TextColumn::make('workflow_stage')
   465	                    ->label('المرحلة')
   466	                    ->formatStateUsing(fn($state) => $state ? "المرحلة {$state}" : '—')
   467	                    ->badge()
   468	                    ->color(fn($state) => match(true) {
   469	                        $state >= 8 => 'success',
   470	                        $state >= 6 => 'info',
   471	                        $state >= 3 => 'warning',
   472	                        default => 'gray',
   473	                    })
   474	                    ->sortable(),
   475	
   476	                Tables\Columns\TextColumn::make('application_date')
   477	                    ->label('تاريخ التقديم')
   478	                    ->date('Y-m-d')
   479	                    ->sortable()
   480	                    ->toggleable(),
   481	
   482	                Tables\Columns\TextColumn::make('payment_date')
   483	                    ->label('تاريخ السداد')
   484	                    ->date('Y-m-d')
   485	                    ->sortable()
   486	                    ->toggleable(isToggledHiddenByDefault: true),
   487	
   488	                Tables\Columns\TextColumn::make('created_at')
   489	                    ->label('تاريخ الإنشاء')
   490	                    ->dateTime('Y-m-d H:i')
   491	                    ->sortable()
   492	                    ->toggleable(isToggledHiddenByDefault: true),
   493	            ])
   494	            ->defaultSort('created_at', 'desc')
   495	            ->filters([
   496	                Tables\Filters\SelectFilter::make('membership_status')
   497	                    ->label('حالة العضوية')
   498	                    ->options(MembershipStatus::toFilamentOptions())
   499	                    ->multiple()
   500	                    ->preload(),
   501	
   502	                Tables\Filters\SelectFilter::make('membership_type_id')
   503	                    ->label('نوع العضوية')
   504	                    ->relationship('membershipType', 'name_ar')
   505	                    ->preload()
   506	                    ->multiple(),
   507	
   508	                Tables\Filters\SelectFilter::make('gender')
   509	                    ->label('النوع')
   510	                    ->options(Gender::toFilamentOptions()),
   511	
   512	                Tables\Filters\SelectFilter::make('workflow_stage')
   513	                    ->label('مرحلة سير العمل')
   514	                    ->options([
   515	                        1 => 'المرحلة 1: تقديم الاستمارة',
   516	                        2 => 'المرحلة 2: تجميع المستندات',
   517	                        3 => 'المرحلة 3: سداد رسوم الاستمارة',
   518	                        4 => 'المرحلة 4: مراجعة الطلب',
   519	                        5 => 'المرحلة 5: المقابلة',
   520	                        6 => 'المرحلة 6: قرار المجلس',
   521	                        7 => 'المرحلة 7: سداد العضوية',
   522	                        8 => 'المرحلة 8: الكارنيهات',
   523	                    ])
   524	                    ->multiple(),
   525	
   526	                Tables\Filters\Filter::make('application_date_range')
   527	                    ->label('فترة التقديم')
   528	                    ->form([
   529	                        Forms\Components\DatePicker::make('from')
   530	                            ->label('من')
   531	                            ->native(false),
   532	                        Forms\Components\DatePicker::make('until')
   533	                            ->label('إلى')
   534	                            ->native(false),
   535	                    ])
   536	                    ->query(function (Builder $query, array $data): Builder {
   537	                        return $query
   538	                            ->when($data['from'], fn(Builder $q, $date) => $q->whereDate('application_date', '>=', $date))
   539	                            ->when($data['until'], fn(Builder $q, $date) => $q->whereDate('application_date', '<=', $date));
   540	                    })
   541	                    ->indicateUsing(function (array $data): array {
   542	                        $indicators = [];
   543	                        if ($data['from'] ?? null) {
   544	                            $indicators[] = Tables\Filters\Indicator::make('من ' . Carbon::parse($data['from'])->format('Y-m-d'))
   545	                                ->removeField('from');
   546	                        }
   547	                        if ($data['until'] ?? null) {
   548	                            $indicators[] = Tables\Filters\Indicator::make('إلى ' . Carbon::parse($data['until'])->format('Y-m-d'))
   549	                                ->removeField('until');
   550	                        }
   551	                        return $indicators;
   552	                    }),
   553	
   554	                Tables\Filters\Filter::make('payment_overdue')
   555	                    ->label('متأخر عن السداد (15 يوم)')
   556	                    ->toggle()
   557	                    ->query(fn(Builder $query) => $query
   558	                        ->where('workflow_stage', 7)
   559	                        ->where('membership_status', MembershipStatus::PENDING)
   560	                        ->whereNotNull('approval_date')
   561	                        ->where('approval_date', '<=', now()->subDays(15))
   562	                    ),
   563	
   564	                Tables\Filters\TrashedFilter::make()
   565	                    ->label('المحذوفات'),
   566	            ])
   567	            ->filtersFormColumns(3)
   568	            ->actions([
   569	                Tables\Actions\ActionGroup::make([
   570	                    Tables\Actions\ViewAction::make()->label('عرض'),
   571	                    Tables\Actions\EditAction::make()->label('تعديل'),
   572	
   573	                    Tables\Actions\Action::make('advance_workflow')
   574	                        ->label('تقديم المرحلة')
   575	                        ->icon('heroicon-o-arrow-left-circle')
   576	                        ->color('success')
   577	                        ->requiresConfirmation()
   578	                        ->modalHeading('تقديم المرحلة التالية')
   579	                        ->modalDescription(fn(Member $record) => "سيتم نقل العضو من المرحلة {$record->workflow_stage} إلى المرحلة " . ($record->workflow_stage + 1))
   580	                        ->visible(fn(Member $record) => $record->workflow_stage < 8 && $record->membership_status === MembershipStatus::PENDING)
   581	                        ->action(function (Member $record) {
   582	                            try {
   583	                                app(MembershipWorkflowService::class)->advanceStage($record);
   584	                                Notification::make()
   585	                                    ->title('تم تقديم المرحلة بنجاح')
   586	                                    ->body("العضو الآن في المرحلة {$record->fresh()->workflow_stage}")
   587	                                    ->success()
   588	                                    ->send();
   589	                            } catch (\Exception $e) {
   590	                                Notification::make()
   591	                                    ->title('خطأ في تقديم المرحلة')
   592	                                    ->body($e->getMessage())
   593	                                    ->danger()
   594	                                    ->send();
   595	                            }
   596	                        }),
   597	
   598	                    Tables\Actions\Action::make('activate')
   599	                        ->label('تفعيل العضوية')
   600	                        ->icon('heroicon-o-check-circle')
   601	                        ->color('success')
   602	                        ->requiresConfirmation()
   603	                        ->visible(fn(Member $record) => $record->membership_status === MembershipStatus::PENDING && $record->workflow_stage >= 7)
   604	                        ->action(function (Member $record) {
   605	                            app(MembershipWorkflowService::class)->activateMembership($record);
   606	                            Notification::make()
   607	                                ->title('تم تفعيل العضوية')
   608	                                ->success()
   609	                                ->send();
   610	                        }),
   611	
   612	                    Tables\Actions\Action::make('suspend')
   613	                        ->label('إيقاف العضوية')
   614	                        ->icon('heroicon-o-pause-circle')
   615	                        ->color('warning')
   616	                        ->requiresConfirmation()
   617	                        ->modalHeading('إيقاف العضوية')
   618	                        ->form([
   619	                            Forms\Components\Textarea::make('reason')
   620	                                ->label('سبب الإيقاف')
   621	                                ->required(),
   622	                        ])
   623	                        ->visible(fn(Member $record) => $record->membership_status === MembershipStatus::ACTIVE)
   624	                        ->action(function (Member $record, array $data) {
   625	                            app(MembershipWorkflowService::class)->suspendMembership($record, $data['reason']);
   626	                            Notification::make()
   627	                                ->title('تم إيقاف العضوية')
   628	                                ->warning()
   629	                                ->send();
   630	                        }),
   631	
   632	                    Tables\Actions\Action::make('reactivate')
   633	                        ->label('إعادة تفعيل')
   634	                        ->icon('heroicon-o-arrow-path')
   635	                        ->color('info')
   636	                        ->requiresConfirmation()
   637	                        ->visible(fn(Member $record) => $record->membership_status === MembershipStatus::SUSPENDED)
   638	                        ->action(function (Member $record) {
   639	                            app(MembershipWorkflowService::class)->reactivateMembership($record);
   640	                            Notification::make()
   641	                                ->title('تم إعادة تفعيل العضوية')
   642	                                ->success()
   643	                                ->send();
   644	                        }),
   645	
   646	                    Tables\Actions\DeleteAction::make()->label('حذف'),
   647	                    Tables\Actions\ForceDeleteAction::make()->label('حذف نهائي'),
   648	                    Tables\Actions\RestoreAction::make()->label('استعادة'),
   649	                ]),
   650	            ])
   651	            ->bulkActions([
   652	                Tables\Actions\BulkActionGroup::make([
   653	                    Tables\Actions\DeleteBulkAction::make()->label('حذف المحدد'),
   654	                    Tables\Actions\ForceDeleteBulkAction::make()->label('حذف نهائي'),
   655	                    Tables\Actions\RestoreBulkAction::make()->label('استعادة المحدد'),
   656	
   657	                    Tables\Actions\BulkAction::make('bulk_activate')
   658	                        ->label('تفعيل المحدد')
   659	                        ->icon('heroicon-o-check-circle')
   660	                        ->color('success')
   661	                        ->requiresConfirmation()
   662	                        ->deselectRecordsAfterCompletion()
   663	                        ->action(function ($records) {
   664	                            $service = app(MembershipWorkflowService::class);
   665	                            $count = 0;
   666	                            foreach ($records as $record) {
   667	                                if ($record->membership_status === MembershipStatus::PENDING && $record->workflow_stage >= 7) {
   668	                                    try {
   669	                                        $service->activateMembership($record);
   670	                                        $count++;
   671	                                    } catch (\Exception $e) {
   672	                                        continue;
   673	                                    }
   674	                                }
   675	                            }
   676	                            Notification::make()
   677	                                ->title("تم تفعيل {$count} عضوية")
   678	                                ->success()
   679	                                ->send();
   680	                        }),
   681	
   682	                    Tables\Actions\BulkAction::make('export_selected')
   683	                        ->label('تصدير المحدد')
   684	                        ->icon('heroicon-o-arrow-down-tray')
   685	                        ->color('gray')
   686	                        ->action(function ($records) {
   687	                            return \Maatwebsite\Excel\Facades\Excel::download(
   688	                                new \App\Exports\MembersExport($records->pluck('id')->toArray()),
   689	                                'members-' . now()->format('Y-m-d-His') . '.xlsx'
   690	                            );
   691	                        }),
   692	                ]),
   693	            ])
   694	            ->emptyStateHeading('لا يوجد أعضاء')
   695	            ->emptyStateDescription('ابدأ بإضافة أعضاء جدد للنادي')
   696	            ->emptyStateIcon('heroicon-o-user-group')
   697	            ->emptyStateActions([
   698	                Tables\Actions\CreateAction::make()
   699	                    ->label('إضافة عضو جديد')
   700	                    ->icon('heroicon-o-plus'),
   701	            ])
   702	            ->striped()
   703	            ->paginated([10, 25, 50, 100])
   704	            ->poll('60s');
   705	    }
   706	
   707	    public static function infolist(Infolist $infolist): Infolist
   708	    {
   709	        return $infolist->schema([
   710	            Infolists\Components\Section::make('بيانات العضو')
   711	                ->columns(4)
   712	                ->schema([
   713	                    Infolists\Components\SpatieMediaLibraryImageEntry::make('photo')
   714	                        ->collection('member_photo')
   715	                        ->label('')
   716	                        ->circular()
   717	                        ->size(100)
   718	                        ->columnSpan(1),
   719	
   720	                    Infolists\Components\Group::make([
   721	                        Infolists\Components\TextEntry::make('full_name_ar')
   722	                            ->label('الاسم (عربي)')
   723	                            ->weight(FontWeight::Bold)
   724	                            ->size(Infolists\Components\TextEntry\TextEntrySize::Large),
   725	
   726	                        Infolists\Components\TextEntry::make('full_name_en')
   727	                            ->label('الاسم (إنجليزي)')
   728	                            ->placeholder('—'),
   729	
   730	                        Infolists\Components\TextEntry::make('membership_number')
   731	                            ->label('رقم العضوية')
   732	                            ->badge()
   733	                            ->color('primary')
   734	                            ->weight(FontWeight::Bold)
   735	                            ->placeholder('لم يُصدر بعد'),
   736	                    ])->columnSpan(3),
   737	                ]),
   738	
   739	            Infolists\Components\Tabs::make('details_tabs')
   740	                ->tabs([
   741	                    Infolists\Components\Tabs\Tab::make('البيانات الشخصية')
   742	                        ->icon('heroicon-o-user')
   743	                        ->schema([
   744	                            Infolists\Components\Grid::make(3)->schema([
   745	                                Infolists\Components\TextEntry::make('national_id')
   746	                                    ->label('الرقم القومي')
   747	                                    ->copyable()
   748	                                    ->fontFamily('mono'),
   749	
   750	                                Infolists\Components\TextEntry::make('gender')
   751	                                    ->label('النوع')
   752	                                    ->badge(),
   753	
   754	                                Infolists\Components\TextEntry::make('date_of_birth')
   755	                                    ->label('تاريخ الميلاد')
   756	                                    ->date('Y-m-d'),
   757	
   758	                                Infolists\Components\TextEntry::make('age')
   759	                                    ->label('العمر')
   760	                                    ->state(fn(Member $record) => $record->date_of_birth?->age . ' سنة'),
   761	
   762	                                Infolists\Components\TextEntry::make('blood_type')
   763	                                    ->label('فصيلة الدم')
   764	                                    ->badge()
   765	                                    ->color('danger')
   766	                                    ->placeholder('—'),
   767	
   768	                                Infolists\Components\TextEntry::make('religion')
   769	                                    ->label('الديانة')
   770	                                    ->formatStateUsing(fn($state) => match($state) {
   771	                                        'muslim' => 'مسلم',
   772	                                        'christian' => 'مسيحي',
   773	                                        'other' => 'أخرى',
   774	                                        default => '—',
   775	                                    }),
   776	
   777	                                Infolists\Components\TextEntry::make('marital_status')
   778	                                    ->label('الحالة الاجتماعية')
   779	                                    ->formatStateUsing(fn($state) => match($state) {
   780	                                        'single' => 'أعزب',
   781	                                        'married' => 'متزوج',
   782	                                        'divorced' => 'مطلق',
   783	                                        'widowed' => 'أرمل',
   784	                                        default => '—',
   785	                                    }),
   786	
   787	                                Infolists\Components\TextEntry::make('nationality')
   788	                                    ->label('الجنسية'),
   789	                            ]),
   790	                        ]),
   791	
   792	                    Infolists\Components\Tabs\Tab::make('الاتصال والعمل')
   793	                        ->icon('heroicon-o-phone')
   794	                        ->schema([
   795	                            Infolists\Components\Grid::make(3)->schema([
   796	                                Infolists\Components\TextEntry::make('mobile')
   797	                                    ->label('الجوال')
   798	                                    ->icon('heroicon-o-phone')
   799	                                    ->copyable(),
   800	
   801	                                Infolists\Components\TextEntry::make('phone')
   802	                                    ->label('الهاتف')
   803	                                    ->placeholder('—'),
   804	
   805	                                Infolists\Components\TextEntry::make('email')
   806	                                    ->label('البريد الإلكتروني')
   807	                                    ->icon('heroicon-o-envelope')
   808	                                    ->copyable()
   809	                                    ->placeholder('—'),
   810	
   811	                                Infolists\Components\TextEntry::make('address')
   812	                                    ->label('العنوان')
   813	                                    ->columnSpanFull()
   814	                                    ->placeholder('—'),
   815	
   816	                                Infolists\Components\TextEntry::make('workplace')
   817	                                    ->label('جهة العمل')
   818	                                    ->placeholder('—'),
   819	
   820	                                Infolists\Components\TextEntry::make('job_title')
   821	                                    ->label('المسمى الوظيفي')
   822	                                    ->placeholder('—'),
   823	
   824	                                Infolists\Components\TextEntry::make('work_phone')
   825	                                    ->label('هاتف العمل')
   826	                                    ->placeholder('—'),
   827	                            ]),
   828	                        ]),
   829	
   830	                    Infolists\Components\Tabs\Tab::make('العضوية')
   831	                        ->icon('heroicon-o-credit-card')
   832	                        ->schema([
   833	                            Infolists\Components\Grid::make(3)->schema([
   834	                                Infolists\Components\TextEntry::make('membershipType.name_ar')
   835	                                    ->label('نوع العضوية')
   836	                                    ->badge(),
   837	
   838	                                Infolists\Components\TextEntry::make('membership_status')
   839	                                    ->label('الحالة')
   840	                                    ->badge()
   841	                                    ->color(fn(MembershipStatus $state): string => $state->getColor())
   842	                                    ->icon(fn(MembershipStatus $state): string => $state->getIcon()),
   843	
   844	                                Infolists\Components\TextEntry::make('workflow_stage')
   845	                                    ->label('المرحلة')
   846	                                    ->formatStateUsing(fn($state) => match((int)$state) {
   847	                                        1 => 'المرحلة 1: تقديم الاستمارة',
   848	                                        2 => 'المرحلة 2: تجميع المستندات',
   849	                                        3 => 'المرحلة 3: سداد رسوم الاستمارة',
   850	                                        4 => 'المرحلة 4: مراجعة الطلب',
   851	                                        5 => 'المرحلة 5: المقابلة',
   852	                                        6 => 'المرحلة 6: قرار المجلس',
   853	                                        7 => 'المرحلة 7: سداد العضوية',
   854	                                        8 => 'المرحلة 8: الكارنيهات',
   855	                                        default => '—',
   856	                                    })
   857	                                    ->badge()
   858	                                    ->color(fn($state) => match(true) {
   859	                                        $state >= 8 => 'success',
   860	                                        $state >= 6 => 'info',
   861	                                        $state >= 3 => 'warning',
   862	                                        default => 'gray',
   863	                                    }),
   864	
   865	                                Infolists\Components\TextEntry::make('application_date')
   866	                                    ->label('تاريخ التقديم')
   867	                                    ->date('Y-m-d'),
   868	
   869	                                Infolists\Components\TextEntry::make('approval_date')
   870	                                    ->label('تاريخ الموافقة')
   871	                                    ->date('Y-m-d')
   872	                                    ->placeholder('—'),
   873	
   874	                                Infolists\Components\TextEntry::make('payment_date')
   875	                                    ->label('تاريخ السداد')
   876	                                    ->date('Y-m-d')
   877	                                    ->placeholder('—'),
   878	
   879	                                Infolists\Components\TextEntry::make('referee1.full_name_ar')
   880	                                    ->label('المُعرّف الأول')
   881	                                    ->placeholder('—'),
   882	
   883	                                Infolists\Components\TextEntry::make('referee2.full_name_ar')
   884	                                    ->label('المُعرّف الثاني')
   885	                                    ->placeholder('—'),
   886	                            ]),
   887	                        ]),
   888	
   889	                    Infolists\Components\Tabs\Tab::make('الطوارئ')
   890	                        ->icon('heroicon-o-exclamation-triangle')
   891	                        ->schema([
   892	                            Infolists\Components\Grid::make(3)->schema([
   893	                                Infolists\Components\TextEntry::make('emergency_contact_name')
   894	                                    ->label('جهة اتصال الطوارئ')
   895	                                    ->placeholder('—'),
   896	
   897	                                Infolists\Components\TextEntry::make('emergency_contact_phone')
   898	                                    ->label('هاتف الطوارئ')
   899	                                    ->copyable()
   900	                                    ->placeholder('—'),
   901	
   902	                                Infolists\Components\TextEntry::make('emergency_contact_relation')
   903	                                    ->label('صلة القرابة')
   904	                                    ->placeholder('—'),
   905	                            ]),
   906	                        ]),
   907	
   908	                    Infolists\Components\Tabs\Tab::make('الملاحظات')
   909	                        ->icon('heroicon-o-document-text')
   910	                        ->schema([
   911	                            Infolists\Components\TextEntry::make('notes')
   912	                                ->label('ملاحظات')
   913	                                ->html()
   914	                                ->placeholder('لا توجد ملاحظات')
   915	                                ->columnSpanFull(),
   916	                        ]),
   917	                ])
   918	                ->columnSpanFull(),
   919	
   920	            // Payment Deadline Warning
   921	            Infolists\Components\Section::make('⚠️ تنبيه: مهلة السداد')
   922	                ->schema([
   923	                    Infolists\Components\TextEntry::make('payment_deadline')
   924	                        ->label('الموعد النهائي للسداد')
   925	                        ->state(function (Member $record) {
   926	                            if ($record->workflow_stage !== 7 || !$record->approval_date) {
   927	                                return null;
   928	                            }
   929	                            $deadline = Carbon::parse($record->approval_date)->addDays(15);
   930	                            $remaining = now()->diffInDays($deadline, false);
   931	                            if ($remaining < 0) {
   932	                                return "⛔ انتهت المهلة منذ " . abs($remaining) . " يوم — يجب إلغاء الطلب";
   933	                            }
   934	                            return "📅 {$deadline->format('Y-m-d')} — متبقي {$remaining} يوم";
   935	                        })
   936	                        ->color(function (Member $record) {
   937	                            if ($record->workflow_stage !== 7 || !$record->approval_date) {
   938	                                return 'gray';
   939	                            }
   940	                            $remaining = now()->diffInDays(Carbon::parse($record->approval_date)->addDays(15), false);
   941	                            if ($remaining < 0) return 'danger';
   942	                            if ($remaining <= 3) return 'warning';
   943	                            return 'success';
   944	                        })
   945	                        ->placeholder('—'),
   946	                ])
   947	                ->visible(fn(Member $record) => $record->workflow_stage === 7 && $record->approval_date)
   948	                ->collapsible(),
   949	        ]);
   950	    }
   951	
   952	    public static function getRelations(): array
   953	    {
   954	        return [
   955	            RelationManagers\DependentsRelationManager::class,
   956	            RelationManagers\ReceiptsRelationManager::class,
   957	            RelationManagers\DocumentsRelationManager::class,
   958	            RelationManagers\SubscriptionsRelationManager::class,
   959	            RelationManagers\CardsRelationManager::class,
   960	            RelationManagers\ViolationsRelationManager::class,
   961	            RelationManagers\NotesRelationManager::class,
   962	            RelationManagers\ActivityLogRelationManager::class,
   963	        ];
   964	    }
   965	
   966	    public static function getPages(): array
   967	    {
   968	        return [
   969	            'index' => Pages\ListMembers::route('/'),
   970	            'create' => Pages\CreateMember::route('/create'),
   971	            'view' => Pages\ViewMember::route('/{record}'),
   972	            'edit' => Pages\EditMember::route('/{record}/edit'),
   973	            'workflow' => Pages\MemberWorkflow::route('/{record}/workflow'),
   974	        ];
   975	    }
   976	
   977	    public static function getEloquentQuery(): Builder
   978	    {
   979	        return parent::getEloquentQuery()
   980	            ->withoutGlobalScopes([SoftDeletingScope::class]);
   981	    }
   982	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [133]: app/Filament/Resources/MemberResource.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [134/494]: app/Filament/Resources/MemberResource/Pages/CreateMember.php
│ LANGUAGE: php | LINES: 47 | SIZE: 1544 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Filament\Resources\MemberResource\Pages;
     4	
     5	use App\Enums\MembershipStatus;
     6	use App\Enums\WorkflowProgressStatus;
     7	use App\Filament\Resources\MemberResource;
     8	use App\Services\Membership\MembershipWorkflowService;
     9	use Filament\Resources\Pages\CreateRecord;
    10	use Filament\Notifications\Notification;
    11	
    12	class CreateMember extends CreateRecord
    13	{
    14	    protected static string $resource = MemberResource::class;
    15	
    16	    protected function getRedirectUrl(): string
    17	    {
    18	        return $this->getResource()::getUrl('view', ['record' => $this->record]);
    19	    }
    20	
    21	    protected function getCreatedNotificationTitle(): ?string
    22	    {
    23	        return 'تم إنشاء ملف العضو بنجاح';
    24	    }
    25	
    26	    protected function mutateFormDataBeforeCreate(array $data): array
    27	    {
    28	        $data['membership_status'] = MembershipStatus::PENDING->value;
    29	        $data['workflow_stage'] = 1;
    30	        $data['workflow_status'] = WorkflowProgressStatus::PENDING->value;
    31	        $data['application_date'] = $data['application_date'] ?? now()->toDateString();
    32	        $data['created_by'] = auth()->id();
    33	
    34	        return $data;
    35	    }
    36	
    37	    protected function afterCreate(): void
    38	    {
    39	        // Initialize workflow progress records
    40	        app(MembershipWorkflowService::class)->initializeWorkflow($this->record);
    41	
    42	        activity()
    43	            ->performedOn($this->record)
    44	            ->causedBy(auth()->user())
    45	            ->withProperties(['stage' => 1])
    46	            ->log('تم إنشاء ملف العضو وبدء مسار التقديم');
    47	    }
    48	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [134]: app/Filament/Resources/MemberResource/Pages/CreateMember.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [135/494]: app/Filament/Resources/MemberResource/Pages/EditMember.php
│ LANGUAGE: php | LINES: 53 | SIZE: 1607 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Filament\Resources\MemberResource\Pages;
     4	
     5	use App\Filament\Resources\MemberResource;
     6	use Filament\Actions;
     7	use Filament\Resources\Pages\EditRecord;
     8	
     9	class EditMember extends EditRecord
    10	{
    11	    protected static string $resource = MemberResource::class;
    12	
    13	    protected function getHeaderActions(): array
    14	    {
    15	        return [
    16	            Actions\ViewAction::make()->label('عرض'),
    17	
    18	            Actions\Action::make('workflow')
    19	                ->label('مسار العضوية')
    20	                ->icon('heroicon-o-arrows-right-left')
    21	                ->color('info')
    22	                ->url(fn() => MemberResource::getUrl('workflow', ['record' => $this->record])),
    23	
    24	            Actions\DeleteAction::make()->label('حذف'),
    25	            Actions\ForceDeleteAction::make()->label('حذف نهائي'),
    26	            Actions\RestoreAction::make()->label('استعادة'),
    27	        ];
    28	    }
    29	
    30	    protected function getRedirectUrl(): string
    31	    {
    32	        return $this->getResource()::getUrl('view', ['record' => $this->record]);
    33	    }
    34	
    35	    protected function getSavedNotificationTitle(): ?string
    36	    {
    37	        return 'تم تحديث بيانات العضو بنجاح';
    38	    }
    39	
    40	    protected function mutateFormDataBeforeSave(array $data): array
    41	    {
    42	        $data['updated_by'] = auth()->id();
    43	        return $data;
    44	    }
    45	
    46	    protected function afterSave(): void
    47	    {
    48	        activity()
    49	            ->performedOn($this->record)
    50	            ->causedBy(auth()->user())
    51	            ->withProperties($this->record->getChanges())
    52	            ->log('تم تحديث بيانات العضو');
    53	    }
    54	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [135]: app/Filament/Resources/MemberResource/Pages/EditMember.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [136/494]: app/Filament/Resources/MemberResource/Pages/ListMembers.php
│ LANGUAGE: php | LINES: 115 | SIZE: 5343 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Filament\Resources\MemberResource\Pages;
     4	
     5	use App\Enums\MembershipStatus;
     6	use App\Filament\Resources\MemberResource;
     7	use Filament\Actions;
     8	use Filament\Resources\Pages\ListRecords;
     9	use Filament\Resources\Components\Tab;
    10	use Illuminate\Database\Eloquent\Builder;
    11	
    12	class ListMembers extends ListRecords
    13	{
    14	    protected static string $resource = MemberResource::class;
    15	
    16	    protected function getHeaderActions(): array
    17	    {
    18	        return [
    19	            Actions\CreateAction::make()
    20	                ->label('عضو جديد')
    21	                ->icon('heroicon-o-plus'),
    22	
    23	            Actions\Action::make('export_all')
    24	                ->label('تصدير الكل')
    25	                ->icon('heroicon-o-arrow-down-tray')
    26	                ->color('gray')
    27	                ->action(function () {
    28	                    return \Maatwebsite\Excel\Facades\Excel::download(
    29	                        new \App\Exports\MembersExport(),
    30	                        'all-members-' . now()->format('Y-m-d-His') . '.xlsx'
    31	                    );
    32	                }),
    33	
    34	            Actions\Action::make('import')
    35	                ->label('استيراد')
    36	                ->icon('heroicon-o-arrow-up-tray')
    37	                ->color('gray')
    38	                ->form([
    39	                    \Filament\Forms\Components\FileUpload::make('file')
    40	                        ->label('ملف Excel')
    41	                        ->acceptedFileTypes(['application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', 'application/vnd.ms-excel'])
    42	                        ->required(),
    43	                ])
    44	                ->action(function (array $data) {
    45	                    \Maatwebsite\Excel\Facades\Excel::import(
    46	                        new \App\Imports\MembersImport(),
    47	                        $data['file']
    48	                    );
    49	                    \Filament\Notifications\Notification::make()
    50	                        ->title('تم استيراد البيانات بنجاح')
    51	                        ->success()
    52	                        ->send();
    53	                }),
    54	        ];
    55	    }
    56	
    57	    public function getTabs(): array
    58	    {
    59	        return [
    60	            'all' => Tab::make('الكل')
    61	                ->badge(fn() => \App\Models\Member::count())
    62	                ->icon('heroicon-o-user-group'),
    63	
    64	            'pending' => Tab::make('قيد الإجراء')
    65	                ->badge(fn() => \App\Models\Member::where('membership_status', MembershipStatus::PENDING)->count())
    66	                ->badgeColor('warning')
    67	                ->icon('heroicon-o-clock')
    68	                ->modifyQueryUsing(fn(Builder $query) => $query->where('membership_status', MembershipStatus::PENDING)),
    69	
    70	            'active' => Tab::make('نشط')
    71	                ->badge(fn() => \App\Models\Member::where('membership_status', MembershipStatus::ACTIVE)->count())
    72	                ->badgeColor('success')
    73	                ->icon('heroicon-o-check-circle')
    74	                ->modifyQueryUsing(fn(Builder $query) => $query->where('membership_status', MembershipStatus::ACTIVE)),
    75	
    76	            'suspended' => Tab::make('موقوف')
    77	                ->badge(fn() => \App\Models\Member::where('membership_status', MembershipStatus::SUSPENDED)->count())
    78	                ->badgeColor('danger')
    79	                ->icon('heroicon-o-pause-circle')
    80	                ->modifyQueryUsing(fn(Builder $query) => $query->where('membership_status', MembershipStatus::SUSPENDED)),
    81	
    82	            'frozen' => Tab::make('مجمّد')
    83	                ->badge(fn() => \App\Models\Member::where('membership_status', MembershipStatus::FROZEN)->count())
    84	                ->badgeColor('info')
    85	                ->icon('heroicon-o-stop-circle')
    86	                ->modifyQueryUsing(fn(Builder $query) => $query->where('membership_status', MembershipStatus::FROZEN)),
    87	
    88	            'cancelled' => Tab::make('ملغي')
    89	                ->badge(fn() => \App\Models\Member::where('membership_status', MembershipStatus::CANCELLED)->count())
    90	                ->badgeColor('gray')
    91	                ->icon('heroicon-o-x-circle')
    92	                ->modifyQueryUsing(fn(Builder $query) => $query->where('membership_status', MembershipStatus::CANCELLED)),
    93	
    94	            'deceased' => Tab::make('متوفي')
    95	                ->badge(fn() => \App\Models\Member::where('membership_status', MembershipStatus::DECEASED)->count())
    96	                ->badgeColor('gray')
    97	                ->icon('heroicon-o-heart')
    98	                ->modifyQueryUsing(fn(Builder $query) => $query->where('membership_status', MembershipStatus::DECEASED)),
    99	
   100	            'payment_overdue' => Tab::make('متأخر السداد')
   101	                ->badge(fn() => \App\Models\Member::where('workflow_stage', 7)
   102	                    ->where('membership_status', MembershipStatus::PENDING)
   103	                    ->whereNotNull('approval_date')
   104	                    ->where('approval_date', '<=', now()->subDays(15))
   105	                    ->count())
   106	                ->badgeColor('danger')
   107	                ->icon('heroicon-o-exclamation-triangle')
   108	                ->modifyQueryUsing(fn(Builder $query) => $query
   109	                    ->where('workflow_stage', 7)
   110	                    ->where('membership_status', MembershipStatus::PENDING)
   111	                    ->whereNotNull('approval_date')
   112	                    ->where('approval_date', '<=', now()->subDays(15))
   113	                ),
   114	        ];
   115	    }
   116	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [136]: app/Filament/Resources/MemberResource/Pages/ListMembers.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [137/494]: app/Filament/Resources/MemberResource/Pages/MemberWorkflow.php
│ LANGUAGE: php | LINES: 173 | SIZE: 6389 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Filament\Resources\MemberResource\Pages;
     4	
     5	use App\Enums\BoardDecisionResult;
     6	use App\Enums\InterviewResult;
     7	use App\Enums\InterviewStatus;
     8	use App\Enums\MembershipStatus;
     9	use App\Enums\WorkflowProgressStatus;
    10	use App\Filament\Resources\MemberResource;
    11	use App\Models\Member;
    12	use App\Models\WorkflowProgress;
    13	use App\Services\Membership\MembershipWorkflowService;
    14	use Filament\Forms;
    15	use Filament\Forms\Form;
    16	use Filament\Notifications\Notification;
    17	use Filament\Resources\Pages\Page;
    18	use Filament\Actions;
    19	use Illuminate\Contracts\Support\Htmlable;
    20	
    21	class MemberWorkflow extends Page
    22	{
    23	    protected static string $resource = MemberResource::class;
    24	
    25	    protected static string $view = 'filament.resources.member-resource.pages.member-workflow';
    26	
    27	    public ?Member $record = null;
    28	
    29	    public ?array $stageData = [];
    30	
    31	    public function getTitle(): string|Htmlable
    32	    {
    33	        return "مسار عضوية: {$this->record->full_name_ar}";
    34	    }
    35	
    36	    public function getBreadcrumb(): string
    37	    {
    38	        return 'مسار العضوية';
    39	    }
    40	
    41	    protected function getHeaderActions(): array
    42	    {
    43	        return [
    44	            Actions\Action::make('back_to_view')
    45	                ->label('العودة للملف')
    46	                ->icon('heroicon-o-arrow-right')
    47	                ->url(MemberResource::getUrl('view', ['record' => $this->record]))
    48	                ->color('gray'),
    49	        ];
    50	    }
    51	
    52	    public function getWorkflowStages(): array
    53	    {
    54	        $stages = [
    55	            1 => [
    56	                'title' => 'تقديم الاستمارة',
    57	                'title_en' => 'Application Form',
    58	                'icon' => 'heroicon-o-document-text',
    59	                'description' => 'تعبئة استمارة العضوية والبيانات الأساسية',
    60	            ],
    61	            2 => [
    62	                'title' => 'تجميع المستندات',
    63	                'title_en' => 'Document Collection',
    64	                'icon' => 'heroicon-o-folder-open',
    65	                'description' => 'رفع جميع المستندات المطلوبة والتحقق منها',
    66	            ],
    67	            3 => [
    68	                'title' => 'سداد رسوم الاستمارة',
    69	                'title_en' => 'Application Fee',
    70	                'icon' => 'heroicon-o-banknotes',
    71	                'description' => 'سداد رسوم تقديم الاستمارة',
    72	            ],
    73	            4 => [
    74	                'title' => 'مراجعة الطلب',
    75	                'title_en' => 'Application Review',
    76	                'icon' => 'heroicon-o-clipboard-document-check',
    77	                'description' => 'مراجعة الطلب من قبل الإدارة المختصة',
    78	            ],
    79	            5 => [
    80	                'title' => 'المقابلة الشخصية',
    81	                'title_en' => 'Interview',
    82	                'icon' => 'heroicon-o-chat-bubble-left-right',
    83	                'description' => 'إجراء المقابلة الشخصية مع المتقدم',
    84	            ],
    85	            6 => [
    86	                'title' => 'قرار مجلس الإدارة',
    87	                'title_en' => 'Board Decision',
    88	                'icon' => 'heroicon-o-shield-check',
    89	                'description' => 'اتخاذ القرار النهائي من مجلس الإدارة',
    90	            ],
    91	            7 => [
    92	                'title' => 'سداد رسوم العضوية',
    93	                'title_en' => 'Membership Payment',
    94	                'icon' => 'heroicon-o-credit-card',
    95	                'description' => 'سداد كافة رسوم العضوية والاشتراكات',
    96	            ],
    97	            8 => [
    98	                'title' => 'إصدار الكارنيهات',
    99	                'title_en' => 'Card Issuance',
   100	                'icon' => 'heroicon-o-identification',
   101	                'description' => 'إصدار وطباعة بطاقات العضوية',
   102	            ],
   103	        ];
   104	
   105	        // Enrich with progress data
   106	        $progressRecords = WorkflowProgress::where('member_id', $this->record->id)
   107	            ->get()
   108	            ->keyBy('stage_number');
   109	
   110	        foreach ($stages as $num => &$stage) {
   111	            $progress = $progressRecords->get($num);
   112	            $stage['status'] = $progress?->status ?? WorkflowProgressStatus::PENDING;
   113	            $stage['completed_at'] = $progress?->completed_at;
   114	            $stage['completed_by'] = $progress?->completedBy?->name ?? null;
   115	            $stage['notes'] = $progress?->notes;
   116	            $stage['is_current'] = $num === $this->record->workflow_stage;
   117	            $stage['is_completed'] = $num < $this->record->workflow_stage;
   118	            $stage['is_future'] = $num > $this->record->workflow_stage;
   119	        }
   120	
   121	        return $stages;
   122	    }
   123	
   124	    public function advanceToNextStage(): void
   125	    {
   126	        try {
   127	            app(MembershipWorkflowService::class)->advanceStage($this->record);
   128	
   129	            Notification::make()
   130	                ->title('تم تقديم المرحلة بنجاح')
   131	                ->body("العضو الآن في المرحلة {$this->record->fresh()->workflow_stage}")
   132	                ->success()
   133	                ->send();
   134	
   135	            $this->record->refresh();
   136	        } catch (\Exception $e) {
   137	            Notification::make()
   138	                ->title('خطأ')
   139	                ->body($e->getMessage())
   140	                ->danger()
   141	                ->send();
   142	        }
   143	    }
   144	
   145	    public function completeCurrentStage(array $data = []): void
   146	    {
   147	        try {
   148	            $service = app(MembershipWorkflowService::class);
   149	
   150	            $stage = $this->record->workflow_stage;
   151	
   152	            // Stage-specific processing
   153	            match ($stage) {
   154	                5 => $service->recordInterview($this->record, $data),
   155	                6 => $service->recordBoardDecision($this->record, $data),
   156	                7 => $service->processPayment($this->record, $data),
   157	                default => $service->advanceStage($this->record, $data['notes'] ?? null),
   158	            };
   159	
   160	            Notification::make()
   161	                ->title('تم إكمال المرحلة بنجاح')
   162	                ->success()
   163	                ->send();
   164	
   165	            $this->record->refresh();
   166	        } catch (\Exception $e) {
   167	            Notification::make()
   168	                ->title('خطأ')
   169	                ->body($e->getMessage())
   170	                ->danger()
   171	                ->send();
   172	        }
   173	    }
   174	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [137]: app/Filament/Resources/MemberResource/Pages/MemberWorkflow.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [138/494]: app/Filament/Resources/MemberResource/Pages/ViewMember.php
│ LANGUAGE: php | LINES: 61 | SIZE: 2713 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Filament\Resources\MemberResource\Pages;
     4	
     5	use App\Enums\MembershipStatus;
     6	use App\Filament\Resources\MemberResource;
     7	use App\Services\Membership\MembershipWorkflowService;
     8	use Filament\Actions;
     9	use Filament\Resources\Pages\ViewRecord;
    10	use Filament\Notifications\Notification;
    11	
    12	class ViewMember extends ViewRecord
    13	{
    14	    protected static string $resource = MemberResource::class;
    15	
    16	    protected function getHeaderActions(): array
    17	    {
    18	        return [
    19	            Actions\EditAction::make()->label('تعديل'),
    20	
    21	            Actions\Action::make('workflow')
    22	                ->label('مسار العضوية')
    23	                ->icon('heroicon-o-arrows-right-left')
    24	                ->color('info')
    25	                ->url(fn() => MemberResource::getUrl('workflow', ['record' => $this->record])),
    26	
    27	            Actions\Action::make('print_profile')
    28	                ->label('طباعة الملف')
    29	                ->icon('heroicon-o-printer')
    30	                ->color('gray')
    31	                ->url(fn() => route('members.print-profile', $this->record), shouldOpenInNewTab: true),
    32	
    33	            Actions\Action::make('advance_workflow')
    34	                ->label('تقديم المرحلة')
    35	                ->icon('heroicon-o-arrow-left-circle')
    36	                ->color('success')
    37	                ->requiresConfirmation()
    38	                ->modalHeading('تقديم المرحلة التالية')
    39	                ->modalDescription(fn() => "سيتم نقل العضو من المرحلة {$this->record->workflow_stage} إلى المرحلة " . ($this->record->workflow_stage + 1))
    40	                ->visible(fn() => $this->record->workflow_stage < 8 && $this->record->membership_status === MembershipStatus::PENDING)
    41	                ->action(function () {
    42	                    try {
    43	                        app(MembershipWorkflowService::class)->advanceStage($this->record);
    44	                        Notification::make()
    45	                            ->title('تم تقديم المرحلة بنجاح')
    46	                            ->body("العضو الآن في المرحلة {$this->record->fresh()->workflow_stage}")
    47	                            ->success()
    48	                            ->send();
    49	                        $this->refreshFormData(['workflow_stage', 'workflow_status']);
    50	                    } catch (\Exception $e) {
    51	                        Notification::make()
    52	                            ->title('خطأ في تقديم المرحلة')
    53	                            ->body($e->getMessage())
    54	                            ->danger()
    55	                            ->send();
    56	                    }
    57	                }),
    58	
    59	            Actions\DeleteAction::make()->label('حذف'),
    60	        ];
    61	    }
    62	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [138]: app/Filament/Resources/MemberResource/Pages/ViewMember.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [139/494]: app/Filament/Resources/MemberResource/RelationManagers/ActivityLogRelationManager.php
│ LANGUAGE: php | LINES: 62 | SIZE: 2454 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Filament\Resources\MemberResource\RelationManagers;
     4	
     5	use Filament\Resources\RelationManagers\RelationManager;
     6	use Filament\Tables;
     7	use Filament\Tables\Table;
     8	
     9	class ActivityLogRelationManager extends RelationManager
    10	{
    11	    protected static string $relationship = 'activities';
    12	
    13	    protected static ?string $title = 'سجل النشاط';
    14	
    15	    protected static ?string $modelLabel = 'نشاط';
    16	
    17	    protected static ?string $pluralModelLabel = 'سجل النشاط';
    18	
    19	    protected static ?string $icon = 'heroicon-o-clock';
    20	
    21	    public function table(Table $table): Table
    22	    {
    23	        return $table
    24	            ->columns([
    25	                Tables\Columns\TextColumn::make('description')
    26	                    ->label('الحدث')
    27	                    ->wrap()
    28	                    ->searchable(),
    29	
    30	                Tables\Columns\TextColumn::make('causer.name')
    31	                    ->label('بواسطة')
    32	                    ->placeholder('النظام'),
    33	
    34	                Tables\Columns\TextColumn::make('properties')
    35	                    ->label('التفاصيل')
    36	                    ->formatStateUsing(function ($state) {
    37	                        if (empty($state)) return '—';
    38	                        $props = is_string($state) ? json_decode($state, true) : $state;
    39	                        if (isset($props['attributes'])) {
    40	                            $changes = [];
    41	                            foreach ($props['attributes'] as $key => $value) {
    42	                                $old = $props['old'][$key] ?? '—';
    43	                                $changes[] = "{$key}: {$old} → {$value}";
    44	                            }
    45	                            return implode(', ', array_slice($changes, 0, 3));
    46	                        }
    47	                        return json_encode($props, JSON_UNESCAPED_UNICODE);
    48	                    })
    49	                    ->limit(80)
    50	                    ->tooltip(fn($record) => json_encode($record->properties, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT))
    51	                    ->toggleable(),
    52	
    53	                Tables\Columns\TextColumn::make('created_at')
    54	                    ->label('التاريخ')
    55	                    ->dateTime('Y-m-d H:i:s')
    56	                    ->sortable(),
    57	            ])
    58	            ->defaultSort('created_at', 'desc')
    59	            ->paginated([10, 25, 50])
    60	            ->emptyStateHeading('لا يوجد سجل نشاط')
    61	            ->emptyStateIcon('heroicon-o-clock');
    62	    }
    63	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [139]: app/Filament/Resources/MemberResource/RelationManagers/ActivityLogRelationManager.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [140/494]: app/Filament/Resources/MemberResource/RelationManagers/CardsRelationManager.php
│ LANGUAGE: php | LINES: 162 | SIZE: 6949 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Filament\Resources\MemberResource\RelationManagers;
     4	
     5	use App\Enums\CardStatus;
     6	use App\Models\MemberCard;
     7	use App\Services\Cards\CardService;
     8	use Filament\Forms;
     9	use Filament\Forms\Form;
    10	use Filament\Notifications\Notification;
    11	use Filament\Resources\RelationManagers\RelationManager;
    12	use Filament\Tables;
    13	use Filament\Tables\Table;
    14	
    15	class CardsRelationManager extends RelationManager
    16	{
    17	    protected static string $relationship = 'cards';
    18	
    19	    protected static ?string $title = 'الكروت';
    20	
    21	    protected static ?string $modelLabel = 'كارنيه';
    22	
    23	    protected static ?string $pluralModelLabel = 'الكروت';
    24	
    25	    protected static ?string $icon = 'heroicon-o-identification';
    26	
    27	    public function form(Form $form): Form
    28	    {
    29	        return $form
    30	            ->schema([
    31	                Forms\Components\Select::make('card_type')
    32	                    ->label('نوع الكارنيه')
    33	                    ->options([
    34	                        'main' => 'أساسي',
    35	                        'replacement' => 'بدل فاقد/تالف',
    36	                        'renewal' => 'تجديد',
    37	                    ])
    38	                    ->required()
    39	                    ->default('main'),
    40	
    41	                Forms\Components\DatePicker::make('issue_date')
    42	                    ->label('تاريخ الإصدار')
    43	                    ->default(now())
    44	                    ->required(),
    45	
    46	                Forms\Components\DatePicker::make('expiry_date')
    47	                    ->label('تاريخ الانتهاء')
    48	                    ->after('issue_date')
    49	                    ->required(),
    50	
    51	                Forms\Components\Textarea::make('replacement_reason')
    52	                    ->label('سبب الاستبدال')
    53	                    ->rows(2)
    54	                    ->maxLength(500)
    55	                    ->visible(fn (Forms\Get $get) => $get('card_type') === 'replacement'),
    56	
    57	                Forms\Components\Textarea::make('notes')
    58	                    ->label('ملاحظات')
    59	                    ->rows(2)
    60	                    ->maxLength(1000),
    61	            ]);
    62	    }
    63	
    64	    public function table(Table $table): Table
    65	    {
    66	        return $table
    67	            ->columns([
    68	                Tables\Columns\TextColumn::make('card_number')
    69	                    ->label('رقم الكارنيه')
    70	                    ->searchable()
    71	                    ->copyable()
    72	                    ->weight('bold')
    73	                    ->color('primary'),
    74	
    75	                Tables\Columns\TextColumn::make('card_type')
    76	                    ->label('النوع')
    77	                    ->badge()
    78	                    ->formatStateUsing(fn (string $state) => match ($state) {
    79	                        'main' => 'أساسي',
    80	                        'replacement' => 'بدل فاقد',
    81	                        'renewal' => 'تجديد',
    82	                        default => $state,
    83	                    })
    84	                    ->color(fn (string $state) => match ($state) {
    85	                        'main' => 'primary',
    86	                        'replacement' => 'warning',
    87	                        'renewal' => 'info',
    88	                        default => 'gray',
    89	                    }),
    90	
    91	                Tables\Columns\TextColumn::make('status')
    92	                    ->label('الحالة')
    93	                    ->badge()
    94	                    ->formatStateUsing(fn (string $state) => CardStatus::tryFrom($state)?->getLabel() ?? $state)
    95	                    ->color(fn (string $state) => CardStatus::tryFrom($state)?->getColor() ?? 'gray'),
    96	
    97	                Tables\Columns\TextColumn::make('issue_date')
    98	                    ->label('الإصدار')
    99	                    ->date('Y-m-d'),
   100	
   101	                Tables\Columns\TextColumn::make('expiry_date')
   102	                    ->label('الانتهاء')
   103	                    ->date('Y-m-d')
   104	                    ->color(fn (MemberCard $record) => $record->expiry_date < now()->toDateString() ? 'danger' : 'success'),
   105	
   106	                Tables\Columns\IconColumn::make('printed_at')
   107	                    ->label('مطبوع')
   108	                    ->boolean()
   109	                    ->getStateUsing(fn (MemberCard $record) => !is_null($record->printed_at)),
   110	
   111	                Tables\Columns\IconColumn::make('collected_at')
   112	                    ->label('مستلم')
   113	                    ->boolean()
   114	                    ->getStateUsing(fn (MemberCard $record) => !is_null($record->collected_at)),
   115	            ])
   116	            ->filters([
   117	                Tables\Filters\SelectFilter::make('status')
   118	                    ->label('الحالة')
   119	                    ->options(CardStatus::toFilamentOptions()),
   120	            ])
   121	            ->headerActions([
   122	                Tables\Actions\Action::make('issue_card')
   123	                    ->label('إصدار كارنيه')
   124	                    ->icon('heroicon-o-plus-circle')
   125	                    ->action(function () {
   126	                        $member = $this->getOwnerRecord();
   127	                        $cardService = app(CardService::class);
   128	                        $card = $cardService->issueCard($member);
   129	                        Notification::make()->title('تم إصدار الكارنيه رقم: ' . $card->card_number)->success()->send();
   130	                    }),
   131	            ])
   132	            ->actions([
   133	                Tables\Actions\Action::make('preview')
   134	                    ->label('معاينة')
   135	                    ->icon('heroicon-o-eye')
   136	                    ->url(fn (MemberCard $record) => route('filament.admin.pages.card-preview', ['card' => $record->id]))
   137	                    ->openUrlInNewTab(),
   138	
   139	                Tables\Actions\Action::make('mark_printed')
   140	                    ->label('طُبع')
   141	                    ->icon('heroicon-o-printer')
   142	                    ->color('success')
   143	                    ->requiresConfirmation()
   144	                    ->visible(fn (MemberCard $record) => $record->status === CardStatus::ACTIVE->value && is_null($record->printed_at))
   145	                    ->action(function (MemberCard $record) {
   146	                        app(CardService::class)->markAsPrinted($record);
   147	                        Notification::make()->title('تم تسجيل الطباعة')->success()->send();
   148	                    }),
   149	
   150	                Tables\Actions\Action::make('mark_collected')
   151	                    ->label('مُستلم')
   152	                    ->icon('heroicon-o-hand-raised')
   153	                    ->color('success')
   154	                    ->requiresConfirmation()
   155	                    ->visible(fn (MemberCard $record) => in_array($record->status, [CardStatus::PRINTED->value, CardStatus::ACTIVE->value]) && is_null($record->collected_at))
   156	                    ->action(function (MemberCard $record) {
   157	                        app(CardService::class)->markAsCollected($record);
   158	                        Notification::make()->title('تم تسجيل الاستلام')->success()->send();
   159	                    }),
   160	            ])
   161	            ->defaultSort('created_at', 'desc');
   162	    }
   163	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [140]: app/Filament/Resources/MemberResource/RelationManagers/CardsRelationManager.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [141/494]: app/Filament/Resources/MemberResource/RelationManagers/DependentsRelationManager.php
│ LANGUAGE: php | LINES: 244 | SIZE: 10396 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	declare(strict_types=1);
     4	
     5	namespace App\Filament\Resources\MemberResource\RelationManagers;
     6	
     7	use App\Enums\DependentStatus;
     8	use App\Enums\Gender;
     9	use App\Filament\Resources\DependentResource;
    10	use App\Services\Membership\DependentService;
    11	use Filament\Forms;
    12	use Filament\Forms\Form;
    13	use Filament\Notifications\Notification;
    14	use Filament\Resources\RelationManagers\RelationManager;
    15	use Filament\Tables;
    16	use Filament\Tables\Table;
    17	
    18	final class DependentsRelationManager extends RelationManager
    19	{
    20	    protected static string $relationship = 'dependents';
    21	
    22	    protected static ?string $title = 'التابعون';
    23	
    24	    protected static ?string $icon = 'heroicon-o-user-group';
    25	
    26	    protected static ?string $modelLabel = 'تابع';
    27	
    28	    protected static ?string $pluralModelLabel = 'تابعون';
    29	
    30	    public function form(Form $form): Form
    31	    {
    32	        return $form->schema([
    33	            Forms\Components\TextInput::make('full_name_ar')
    34	                ->label('الاسم بالعربية')
    35	                ->required()
    36	                ->maxLength(255),
    37	
    38	            Forms\Components\TextInput::make('full_name_en')
    39	                ->label('الاسم بالإنجليزية')
    40	                ->maxLength(255)
    41	                ->direction('ltr'),
    42	
    43	            Forms\Components\Select::make('relationship')
    44	                ->label('صلة القرابة')
    45	                ->options([
    46	                    'spouse' => 'زوج/زوجة',
    47	                    'child' => 'ابن/ابنة',
    48	                    'parent' => 'أب/أم',
    49	                    'sibling' => 'أخ/أخت',
    50	                    'grandchild' => 'حفيد/حفيدة',
    51	                    'other' => 'أخرى',
    52	                ])
    53	                ->required()
    54	                ->native(false),
    55	
    56	            Forms\Components\Select::make('gender')
    57	                ->label('النوع')
    58	                ->options(Gender::toFilamentOptions())
    59	                ->required()
    60	                ->native(false),
    61	
    62	            Forms\Components\DatePicker::make('date_of_birth')
    63	                ->label('تاريخ الميلاد')
    64	                ->required()
    65	                ->maxDate(now())
    66	                ->displayFormat('d/m/Y')
    67	                ->native(false),
    68	
    69	            Forms\Components\TextInput::make('national_id')
    70	                ->label('رقم الهوية')
    71	                ->maxLength(20)
    72	                ->direction('ltr'),
    73	
    74	            Forms\Components\TextInput::make('phone')
    75	                ->label('الهاتف')
    76	                ->tel()
    77	                ->direction('ltr')
    78	                ->maxLength(20),
    79	
    80	            Forms\Components\FileUpload::make('photo_path')
    81	                ->label('الصورة')
    82	                ->image()
    83	                ->avatar()
    84	                ->directory('dependents/photos')
    85	                ->maxSize(2048)
    86	                ->circleCropper()
    87	                ->columnSpanFull(),
    88	
    89	            Forms\Components\Textarea::make('notes')
    90	                ->label('ملاحظات')
    91	                ->rows(2)
    92	                ->columnSpanFull(),
    93	        ]);
    94	    }
    95	
    96	    public function table(Table $table): Table
    97	    {
    98	        return $table
    99	            ->recordTitleAttribute('full_name_ar')
   100	            ->columns([
   101	                Tables\Columns\ImageColumn::make('photo_path')
   102	                    ->label('')
   103	                    ->circular()
   104	                    ->size(35)
   105	                    ->defaultImageUrl(fn ($record) => 'https://ui-avatars.com/api/?name=' . urlencode($record->full_name_ar) . '&background=random&size=70'),
   106	
   107	                Tables\Columns\TextColumn::make('full_name_ar')
   108	                    ->label('الاسم')
   109	                    ->searchable()
   110	                    ->sortable()
   111	                    ->weight('bold')
   112	                    ->url(fn ($record) => DependentResource::getUrl('view', ['record' => $record]))
   113	                    ->color('primary'),
   114	
   115	                Tables\Columns\TextColumn::make('relationship')
   116	                    ->label('القرابة')
   117	                    ->formatStateUsing(fn (string $state) => match ($state) {
   118	                        'spouse' => 'زوج/زوجة',
   119	                        'child' => 'ابن/ابنة',
   120	                        'parent' => 'أب/أم',
   121	                        'sibling' => 'أخ/أخت',
   122	                        'grandchild' => 'حفيد/حفيدة',
   123	                        'other' => 'أخرى',
   124	                        default => $state,
   125	                    })
   126	                    ->badge()
   127	                    ->color(fn (string $state) => match ($state) {
   128	                        'spouse' => 'info',
   129	                        'child' => 'success',
   130	                        'parent' => 'warning',
   131	                        default => 'gray',
   132	                    }),
   133	
   134	                Tables\Columns\TextColumn::make('date_of_birth')
   135	                    ->label('الميلاد')
   136	                    ->date('d/m/Y')
   137	                    ->description(fn ($record) => $record->date_of_birth ? $record->date_of_birth->age . ' سنة' : null),
   138	
   139	                Tables\Columns\TextColumn::make('phone')
   140	                    ->label('الهاتف')
   141	                    ->toggleable(),
   142	
   143	                Tables\Columns\TextColumn::make('status')
   144	                    ->label('الحالة')
   145	                    ->badge()
   146	                    ->formatStateUsing(fn (DependentStatus $state) => $state->getLabel())
   147	                    ->color(fn (DependentStatus $state) => $state->getColor())
   148	                    ->icon(fn (DependentStatus $state) => $state->getIcon()),
   149	            ])
   150	            ->defaultSort('created_at', 'desc')
   151	            ->filters([
   152	                Tables\Filters\SelectFilter::make('status')
   153	                    ->label('الحالة')
   154	                    ->options(DependentStatus::toFilamentOptions()),
   155	
   156	                Tables\Filters\SelectFilter::make('relationship')
   157	                    ->label('القرابة')
   158	                    ->options([
   159	                        'spouse' => 'زوج/زوجة',
   160	                        'child' => 'ابن/ابنة',
   161	                        'parent' => 'أب/أم',
   162	                        'sibling' => 'أخ/أخت',
   163	                        'grandchild' => 'حفيد/حفيدة',
   164	                        'other' => 'أخرى',
   165	                    ]),
   166	            ])
   167	            ->headerActions([
   168	                Tables\Actions\CreateAction::make()
   169	                    ->label('إضافة تابع')
   170	                    ->icon('heroicon-o-plus')
   171	                    ->before(function (Tables\Actions\CreateAction $action) {
   172	                        $member = $this->ownerRecord;
   173	                        $service = app(DependentService::class);
   174	
   175	                        if (!$service->canAddDependent($member)) {
   176	                            Notification::make()
   177	                                ->danger()
   178	                                ->title('خطأ')
   179	                                ->body('لقد تجاوزت الحد الأقصى لعدد التابعين لهذا العضو.')
   180	                                ->send();
   181	
   182	                            $action->cancel();
   183	                        }
   184	                    })
   185	                    ->mutateFormDataUsing(function (array $data): array {
   186	                        $data['status'] = DependentStatus::PENDING_REVIEW->value;
   187	                        $data['created_by'] = auth()->id();
   188	                        return $data;
   189	                    }),
   190	            ])
   191	            ->actions([
   192	                Tables\Actions\ActionGroup::make([
   193	                    Tables\Actions\ViewAction::make()->label('عرض'),
   194	                    Tables\Actions\EditAction::make()->label('تعديل'),
   195	
   196	                    Tables\Actions\Action::make('approve')
   197	                        ->label('موافقة')
   198	                        ->icon('heroicon-o-check-circle')
   199	                        ->color('success')
   200	                        ->requiresConfirmation()
   201	                        ->action(function ($record) {
   202	                            try {
   203	                                app(DependentService::class)->approveDependent($record);
   204	                                Notification::make()->success()->title('تمت الموافقة')->send();
   205	                            } catch (\Exception $e) {
   206	                                Notification::make()->danger()->title('خطأ')->body($e->getMessage())->send();
   207	                            }
   208	                        })
   209	                        ->visible(fn ($record) => in_array($record->status, [DependentStatus::PENDING_REVIEW, DependentStatus::PENDING_DOCUMENTS])),
   210	
   211	                    Tables\Actions\Action::make('reject')
   212	                        ->label('رفض')
   213	                        ->icon('heroicon-o-x-circle')
   214	                        ->color('danger')
   215	                        ->requiresConfirmation()
   216	                        ->form([
   217	                            Forms\Components\Textarea::make('reason')
   218	                                ->label('سبب الرفض')
   219	                                ->required(),
   220	                        ])
   221	                        ->action(function ($record, array $data) {
   222	                            try {
   223	                                app(DependentService::class)->rejectDependent($record, $data['reason']);
   224	                                Notification::make()->success()->title('تم الرفض')->send();
   225	                            } catch (\Exception $e) {
   226	                                Notification::make()->danger()->title('خطأ')->body($e->getMessage())->send();
   227	                            }
   228	                        })
   229	                        ->visible(fn ($record) => in_array($record->status, [DependentStatus::PENDING_REVIEW, DependentStatus::PENDING_DOCUMENTS])),
   230	
   231	                    Tables\Actions\DeleteAction::make()->label('حذف'),
   232	                ]),
   233	            ])
   234	            ->bulkActions([
   235	                Tables\Actions\DeleteBulkAction::make(),
   236	            ])
   237	            ->emptyStateHeading('لا يوجد تابعون')
   238	            ->emptyStateDescription(function () {
   239	                $service = app(DependentService::class);
   240	                $remaining = $service->getRemainingSlots($this->ownerRecord);
   241	                return "يمكنك إضافة {$remaining} تابع لهذا العضو.";
   242	            })
   243	            ->emptyStateIcon('heroicon-o-user-group');
   244	    }
   245	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [141]: app/Filament/Resources/MemberResource/RelationManagers/DependentsRelationManager.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [142/494]: app/Filament/Resources/MemberResource/RelationManagers/DocumentsRelationManager.php
│ LANGUAGE: php | LINES: 151 | SIZE: 6312 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Filament\Resources\MemberResource\RelationManagers;
     4	
     5	use App\Models\MemberDocument;
     6	use App\Services\Documents\DocumentService;
     7	use Filament\Forms;
     8	use Filament\Forms\Form;
     9	use Filament\Notifications\Notification;
    10	use Filament\Resources\RelationManagers\RelationManager;
    11	use Filament\Tables;
    12	use Filament\Tables\Table;
    13	use Illuminate\Database\Eloquent\Builder;
    14	
    15	class DocumentsRelationManager extends RelationManager
    16	{
    17	    protected static string $relationship = 'documents';
    18	
    19	    protected static ?string $title = 'المستندات';
    20	
    21	    protected static ?string $modelLabel = 'مستند';
    22	
    23	    protected static ?string $pluralModelLabel = 'المستندات';
    24	
    25	    protected static ?string $icon = 'heroicon-o-document-duplicate';
    26	
    27	    public function form(Form $form): Form
    28	    {
    29	        return $form
    30	            ->schema([
    31	                Forms\Components\Select::make('document_type_id')
    32	                    ->label('نوع المستند')
    33	                    ->relationship('documentType', 'name_ar', fn (Builder $query) => $query->where('is_active', true)->where('is_archived', false))
    34	                    ->required()
    35	                    ->searchable()
    36	                    ->preload(),
    37	
    38	                Forms\Components\Select::make('dependent_id')
    39	                    ->label('التابع (اختياري)')
    40	                    ->options(function () {
    41	                        $member = $this->getOwnerRecord();
    42	                        return \App\Models\MemberDependent::where('member_id', $member->id)
    43	                            ->where('is_archived', false)
    44	                            ->pluck('full_name_ar', 'id')
    45	                            ->toArray();
    46	                    })
    47	                    ->placeholder('للعضو نفسه')
    48	                    ->searchable(),
    49	
    50	                Forms\Components\FileUpload::make('file_path')
    51	                    ->label('الملف')
    52	                    ->disk('public')
    53	                    ->directory(fn () => 'documents/members/' . $this->getOwnerRecord()->id)
    54	                    ->required()
    55	                    ->maxSize(10240)
    56	                    ->acceptedFileTypes([
    57	                        'application/pdf', 'image/jpeg', 'image/png', 'image/gif', 'image/webp',
    58	                    ])
    59	                    ->openable()
    60	                    ->downloadable()
    61	                    ->columnSpanFull(),
    62	
    63	                Forms\Components\DatePicker::make('expiry_date')
    64	                    ->label('تاريخ الانتهاء'),
    65	
    66	                Forms\Components\Textarea::make('notes')
    67	                    ->label('ملاحظات')
    68	                    ->rows(2)
    69	                    ->maxLength(500),
    70	            ]);
    71	    }
    72	
    73	    public function table(Table $table): Table
    74	    {
    75	        return $table
    76	            ->columns([
    77	                Tables\Columns\TextColumn::make('documentType.name_ar')
    78	                    ->label('نوع المستند')
    79	                    ->badge()
    80	                    ->color('info'),
    81	
    82	                Tables\Columns\TextColumn::make('dependent.full_name_ar')
    83	                    ->label('التابع')
    84	                    ->placeholder('العضو'),
    85	
    86	                Tables\Columns\TextColumn::make('original_filename')
    87	                    ->label('الملف')
    88	                    ->limit(25),
    89	
    90	                Tables\Columns\IconColumn::make('is_verified')
    91	                    ->label('التحقق')
    92	                    ->boolean()
    93	                    ->trueIcon('heroicon-o-check-badge')
    94	                    ->falseIcon('heroicon-o-clock')
    95	                    ->trueColor('success')
    96	                    ->falseColor('warning'),
    97	
    98	                Tables\Columns\TextColumn::make('expiry_date')
    99	                    ->label('الانتهاء')
   100	                    ->date('Y-m-d')
   101	                    ->placeholder('-')
   102	                    ->color(fn (MemberDocument $record) => $record->expiry_date && $record->expiry_date < now()->toDateString() ? 'danger' : null),
   103	
   104	                Tables\Columns\TextColumn::make('created_at')
   105	                    ->label('تاريخ الرفع')
   106	                    ->dateTime('Y-m-d H:i'),
   107	            ])
   108	            ->filters([
   109	                Tables\Filters\TernaryFilter::make('is_verified')
   110	                    ->label('التحقق'),
   111	            ])
   112	            ->headerActions([
   113	                Tables\Actions\CreateAction::make()
   114	                    ->label('رفع مستند')
   115	                    ->icon('heroicon-o-arrow-up-tray')
   116	                    ->mutateFormDataUsing(function (array $data): array {
   117	                        $data['member_id'] = $this->getOwnerRecord()->id;
   118	                        $data['created_by'] = auth()->id();
   119	                        $data['updated_by'] = auth()->id();
   120	                        return $data;
   121	                    }),
   122	
   123	                Tables\Actions\Action::make('checklist')
   124	                    ->label('قائمة المراجعة')
   125	                    ->icon('heroicon-o-clipboard-document-list')
   126	                    ->url(fn () => route('filament.admin.pages.document-checklist', ['member' => $this->getOwnerRecord()->id]))
   127	                    ->color('info'),
   128	            ])
   129	            ->actions([
   130	                Tables\Actions\Action::make('view_file')
   131	                    ->label('عرض')
   132	                    ->icon('heroicon-o-eye')
   133	                    ->url(fn (MemberDocument $record) => $record->file_path ? asset('storage/' . $record->file_path) : null)
   134	                    ->openUrlInNewTab(),
   135	
   136	                Tables\Actions\Action::make('verify')
   137	                    ->label('تحقق')
   138	                    ->icon('heroicon-o-check-badge')
   139	                    ->color('success')
   140	                    ->requiresConfirmation()
   141	                    ->visible(fn (MemberDocument $record) => !$record->is_verified)
   142	                    ->action(function (MemberDocument $record) {
   143	                        app(DocumentService::class)->verifyDocument($record);
   144	                        Notification::make()->title('تم التحقق')->success()->send();
   145	                    }),
   146	
   147	                Tables\Actions\EditAction::make()->label('تعديل'),
   148	                Tables\Actions\DeleteAction::make()->label('حذف'),
   149	            ])
   150	            ->defaultSort('created_at', 'desc');
   151	    }
   152	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [142]: app/Filament/Resources/MemberResource/RelationManagers/DocumentsRelationManager.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [143/494]: app/Filament/Resources/MemberResource/RelationManagers/NotesRelationManager.php
│ LANGUAGE: php | LINES: 141 | SIZE: 5493 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Filament\Resources\MemberResource\RelationManagers;
     4	
     5	use Filament\Forms;
     6	use Filament\Forms\Form;
     7	use Filament\Resources\RelationManagers\RelationManager;
     8	use Filament\Tables;
     9	use Filament\Tables\Table;
    10	
    11	class NotesRelationManager extends RelationManager
    12	{
    13	    protected static string $relationship = 'memberNotes';
    14	
    15	    protected static ?string $title = 'الملاحظات';
    16	
    17	    protected static ?string $modelLabel = 'ملاحظة';
    18	
    19	    protected static ?string $pluralModelLabel = 'الملاحظات';
    20	
    21	    protected static ?string $icon = 'heroicon-o-chat-bubble-bottom-center-text';
    22	
    23	    public function form(Form $form): Form
    24	    {
    25	        return $form->schema([
    26	            Forms\Components\Select::make('note_type')
    27	                ->label('نوع الملاحظة')
    28	                ->options([
    29	                    'general' => 'عام',
    30	                    'financial' => 'مالي',
    31	                    'disciplinary' => 'تأديبي',
    32	                    'medical' => 'طبي',
    33	                    'administrative' => 'إداري',
    34	                    'complaint' => 'شكوى',
    35	                ])
    36	                ->required()
    37	                ->native(false),
    38	
    39	            Forms\Components\Textarea::make('content')
    40	                ->label('المحتوى')
    41	                ->required()
    42	                ->rows(4)
    43	                ->columnSpanFull(),
    44	
    45	            Forms\Components\Toggle::make('is_pinned')
    46	                ->label('تثبيت')
    47	                ->helperText('الملاحظات المثبتة تظهر أولاً'),
    48	
    49	            Forms\Components\Toggle::make('is_private')
    50	                ->label('خاصة')
    51	                ->helperText('الملاحظات الخاصة تظهر فقط لصاحبها والإدارة'),
    52	        ]);
    53	    }
    54	
    55	    public function table(Table $table): Table
    56	    {
    57	        return $table
    58	            ->recordTitleAttribute('content')
    59	            ->columns([
    60	                Tables\Columns\IconColumn::make('is_pinned')
    61	                    ->label('📌')
    62	                    ->boolean()
    63	                    ->trueIcon('heroicon-s-bookmark')
    64	                    ->falseIcon('heroicon-o-bookmark')
    65	                    ->width('40px'),
    66	
    67	                Tables\Columns\TextColumn::make('note_type')
    68	                    ->label('النوع')
    69	                    ->formatStateUsing(fn($state) => match($state) {
    70	                        'general' => 'عام',
    71	                        'financial' => 'مالي',
    72	                        'disciplinary' => 'تأديبي',
    73	                        'medical' => 'طبي',
    74	                        'administrative' => 'إداري',
    75	                        'complaint' => 'شكوى',
    76	                        default => $state,
    77	                    })
    78	                    ->badge()
    79	                    ->color(fn($state) => match($state) {
    80	                        'financial' => 'success',
    81	                        'disciplinary' => 'danger',
    82	                        'medical' => 'info',
    83	                        'complaint' => 'warning',
    84	                        default => 'gray',
    85	                    }),
    86	
    87	                Tables\Columns\TextColumn::make('content')
    88	                    ->label('المحتوى')
    89	                    ->limit(60)
    90	                    ->tooltip(fn($record) => $record->content)
    91	                    ->wrap(),
    92	
    93	                Tables\Columns\IconColumn::make('is_private')
    94	                    ->label('🔒')
    95	                    ->boolean()
    96	                    ->trueIcon('heroicon-s-lock-closed')
    97	                    ->falseIcon('heroicon-o-lock-open'),
    98	
    99	                Tables\Columns\TextColumn::make('creator.name')
   100	                    ->label('بواسطة'),
   101	
   102	                Tables\Columns\TextColumn::make('created_at')
   103	                    ->label('التاريخ')
   104	                    ->dateTime('Y-m-d H:i')
   105	                    ->sortable(),
   106	            ])
   107	            ->defaultSort('is_pinned', 'desc')
   108	            ->filters([
   109	                Tables\Filters\SelectFilter::make('note_type')
   110	                    ->label('النوع')
   111	                    ->options([
   112	                        'general' => 'عام',
   113	                        'financial' => 'مالي',
   114	                        'disciplinary' => 'تأديبي',
   115	                        'medical' => 'طبي',
   116	                        'administrative' => 'إداري',
   117	                        'complaint' => 'شكوى',
   118	                    ]),
   119	
   120	                Tables\Filters\TernaryFilter::make('is_pinned')
   121	                    ->label('مثبت'),
   122	            ])
   123	            ->headerActions([
   124	                Tables\Actions\CreateAction::make()
   125	                    ->label('إضافة ملاحظة')
   126	                    ->mutateFormDataUsing(function (array $data): array {
   127	                        $data['created_by'] = auth()->id();
   128	                        return $data;
   129	                    }),
   130	            ])
   131	            ->actions([
   132	                Tables\Actions\EditAction::make()
   133	                    ->label('تعديل')
   134	                    ->visible(fn($record) => $record->created_by === auth()->id()),
   135	                Tables\Actions\DeleteAction::make()
   136	                    ->label('حذف')
   137	                    ->visible(fn($record) => $record->created_by === auth()->id() || auth()->user()->hasRole('super_admin')),
   138	            ])
   139	            ->emptyStateHeading('لا توجد ملاحظات')
   140	            ->emptyStateIcon('heroicon-o-chat-bubble-bottom-center-text');
   141	    }
   142	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [143]: app/Filament/Resources/MemberResource/RelationManagers/NotesRelationManager.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [144/494]: app/Filament/Resources/MemberResource/RelationManagers/ReceiptsRelationManager.php
│ LANGUAGE: php | LINES: 123 | SIZE: 4885 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Filament\Resources\MemberResource\RelationManagers;
     4	
     5	use App\Enums\FeeCategory;
     6	use App\Enums\ReceiptStatus;
     7	use Filament\Forms;
     8	use Filament\Forms\Form;
     9	use Filament\Resources\RelationManagers\RelationManager;
    10	use Filament\Tables;
    11	use Filament\Tables\Table;
    12	use Filament\Support\Enums\FontWeight;
    13	
    14	class ReceiptsRelationManager extends RelationManager
    15	{
    16	    protected static string $relationship = 'receipts';
    17	
    18	    protected static ?string $title = 'الإيصالات';
    19	
    20	    protected static ?string $modelLabel = 'إيصال';
    21	
    22	    protected static ?string $pluralModelLabel = 'الإيصالات';
    23	
    24	    protected static ?string $icon = 'heroicon-o-receipt-percent';
    25	
    26	    public function table(Table $table): Table
    27	    {
    28	        return $table
    29	            ->recordTitleAttribute('receipt_number')
    30	            ->columns([
    31	                Tables\Columns\TextColumn::make('receipt_number')
    32	                    ->label('رقم الإيصال')
    33	                    ->searchable()
    34	                    ->sortable()
    35	                    ->copyable()
    36	                    ->weight(FontWeight::Bold)
    37	                    ->color('primary'),
    38	
    39	                Tables\Columns\TextColumn::make('feeCategory')
    40	                    ->label('نوع الرسم')
    41	                    ->formatStateUsing(fn($state) => $state instanceof FeeCategory ? $state->getLabel() : $state)
    42	                    ->badge(),
    43	
    44	                Tables\Columns\TextColumn::make('amount')
    45	                    ->label('المبلغ')
    46	                    ->money('EGP')
    47	                    ->sortable()
    48	                    ->weight(FontWeight::SemiBold),
    49	
    50	                Tables\Columns\TextColumn::make('vat_amount')
    51	                    ->label('ض.ق.م')
    52	                    ->money('EGP')
    53	                    ->toggleable(isToggledHiddenByDefault: true),
    54	
    55	                Tables\Columns\TextColumn::make('total_amount')
    56	                    ->label('الإجمالي')
    57	                    ->money('EGP')
    58	                    ->weight(FontWeight::Bold)
    59	                    ->color('success'),
    60	
    61	                Tables\Columns\TextColumn::make('status')
    62	                    ->label('الحالة')
    63	                    ->badge()
    64	                    ->color(fn(ReceiptStatus $state) => $state->getColor()),
    65	
    66	                Tables\Columns\TextColumn::make('payment_method')
    67	                    ->label('طريقة الدفع')
    68	                    ->formatStateUsing(fn($state) => match($state) {
    69	                        'cash' => 'نقدي',
    70	                        'bank_transfer' => 'تحويل بنكي',
    71	                        'check' => 'شيك',
    72	                        'card' => 'بطاقة',
    73	                        'installment' => 'تقسيط',
    74	                        default => $state,
    75	                    })
    76	                    ->toggleable(),
    77	
    78	                Tables\Columns\TextColumn::make('receipt_date')
    79	                    ->label('التاريخ')
    80	                    ->date('Y-m-d')
    81	                    ->sortable(),
    82	
    83	                Tables\Columns\TextColumn::make('cashier.name')
    84	                    ->label('أمين الصندوق')
    85	                    ->toggleable(isToggledHiddenByDefault: true),
    86	            ])
    87	            ->defaultSort('receipt_date', 'desc')
    88	            ->filters([
    89	                Tables\Filters\SelectFilter::make('status')
    90	                    ->label('الحالة')
    91	                    ->options(ReceiptStatus::toFilamentOptions()),
    92	
    93	                Tables\Filters\SelectFilter::make('fee_category')
    94	                    ->label('نوع الرسم')
    95	                    ->options(FeeCategory::toFilamentOptions()),
    96	            ])
    97	            ->headerActions([
    98	                // Receipts are created through the Financial module, not directly here
    99	            ])
   100	            ->actions([
   101	                Tables\Actions\Action::make('view_receipt')
   102	                    ->label('عرض')
   103	                    ->icon('heroicon-o-eye')
   104	                    ->url(fn($record) => route('receipts.show', $record))
   105	                    ->openUrlInNewTab(),
   106	
   107	                Tables\Actions\Action::make('print_receipt')
   108	                    ->label('طباعة')
   109	                    ->icon('heroicon-o-printer')
   110	                    ->url(fn($record) => route('receipts.print', $record))
   111	                    ->openUrlInNewTab()
   112	                    ->color('gray'),
   113	            ])
   114	            ->emptyStateHeading('لا توجد إيصالات')
   115	            ->emptyStateDescription('سيتم إنشاء الإيصالات تلقائياً عند السداد')
   116	            ->emptyStateIcon('heroicon-o-receipt-percent')
   117	            ->summary([
   118	                Tables\Columns\Summarizers\Sum::make()
   119	                    ->label('الإجمالي')
   120	                    ->money('EGP')
   121	                    ->using(fn($query) => $query->sum('total_amount')),
   122	            ]);
   123	    }
   124	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [144]: app/Filament/Resources/MemberResource/RelationManagers/ReceiptsRelationManager.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [145/494]: app/Filament/Resources/MemberResource/RelationManagers/SubscriptionsRelationManager.php
│ LANGUAGE: php | LINES: 117 | SIZE: 4991 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Filament\Resources\MemberResource\RelationManagers;
     4	
     5	use App\Enums\PaymentMethod;
     6	use App\Enums\SubscriptionStatus;
     7	use App\Models\Subscription;
     8	use App\Services\Subscription\SubscriptionService;
     9	use Filament\Forms;
    10	use Filament\Notifications\Notification;
    11	use Filament\Resources\RelationManagers\RelationManager;
    12	use Filament\Tables;
    13	use Filament\Tables\Table;
    14	
    15	class SubscriptionsRelationManager extends RelationManager
    16	{
    17	    protected static string $relationship = 'subscriptions';
    18	    protected static ?string $title = 'الاشتراكات';
    19	    protected static ?string $modelLabel = 'اشتراك';
    20	
    21	    public function table(Table $table): Table
    22	    {
    23	        return $table
    24	            ->columns([
    25	                Tables\Columns\TextColumn::make('subscriptionPeriod.year')
    26	                    ->label('السنة')
    27	                    ->sortable()
    28	                    ->badge()
    29	                    ->color('gray'),
    30	
    31	                Tables\Columns\TextColumn::make('subscriptionPeriod.name_ar')
    32	                    ->label('الفترة')
    33	                    ->limit(25),
    34	
    35	                Tables\Columns\TextColumn::make('net_amount')
    36	                    ->label('المطلوب')
    37	                    ->money('EGP')
    38	                    ->sortable(),
    39	
    40	                Tables\Columns\TextColumn::make('paid_amount')
    41	                    ->label('المدفوع')
    42	                    ->money('EGP')
    43	                    ->color('success'),
    44	
    45	                Tables\Columns\TextColumn::make('balance')
    46	                    ->label('المتبقي')
    47	                    ->money('EGP')
    48	                    ->color(fn ($state) => $state > 0 ? 'danger' : 'success')
    49	                    ->weight(fn ($state) => $state > 0 ? 'bold' : 'normal'),
    50	
    51	                Tables\Columns\TextColumn::make('status')
    52	                    ->label('الحالة')
    53	                    ->badge()
    54	                    ->formatStateUsing(fn (SubscriptionStatus $state) => $state->label())
    55	                    ->color(fn (SubscriptionStatus $state) => $state->color())
    56	                    ->icon(fn (SubscriptionStatus $state) => $state->icon()),
    57	
    58	                Tables\Columns\TextColumn::make('paid_date')
    59	                    ->label('تاريخ الدفع')
    60	                    ->date('d/m/Y')
    61	                    ->placeholder('—'),
    62	            ])
    63	            ->defaultSort('subscriptionPeriod.year', 'desc')
    64	            ->headerActions([])
    65	            ->actions([
    66	                Tables\Actions\Action::make('view')
    67	                    ->label('عرض')
    68	                    ->icon('heroicon-o-eye')
    69	                    ->url(fn (Subscription $record) => route('filament.admin.resources.subscriptions.view', $record)),
    70	
    71	                Tables\Actions\Action::make('pay')
    72	                    ->label('دفع')
    73	                    ->icon('heroicon-o-banknotes')
    74	                    ->color('success')
    75	                    ->visible(fn (Subscription $record) => $record->status->isPayable())
    76	                    ->form([
    77	                        Forms\Components\TextInput::make('amount')
    78	                            ->label('المبلغ')
    79	                            ->numeric()
    80	                            ->required()
    81	                            ->prefix('ج.م')
    82	                            ->default(fn (Subscription $record) => $record->remaining_balance),
    83	
    84	                        Forms\Components\Select::make('payment_method')
    85	                            ->label('طريقة الدفع')
    86	                            ->options(collect(PaymentMethod::cases())->mapWithKeys(
    87	                                fn ($m) => [$m->value => $m->label()]
    88	                            ))
    89	                            ->default('cash')
    90	                            ->required(),
    91	
    92	                        Forms\Components\TextInput::make('reference_number')
    93	                            ->label('رقم المرجع'),
    94	                    ])
    95	                    ->action(function (Subscription $record, array $data) {
    96	                        try {
    97	                            app(SubscriptionService::class)->recordPayment(
    98	                                subscription: $record,
    99	                                amount: (float) $data['amount'],
   100	                                paymentMethod: $data['payment_method'],
   101	                                referenceNumber: $data['reference_number'] ?? null,
   102	                            );
   103	
   104	                            Notification::make()
   105	                                ->title('تم تسجيل الدفع')
   106	                                ->success()
   107	                                ->send();
   108	                        } catch (\DomainException $e) {
   109	                            Notification::make()
   110	                                ->title('خطأ')
   111	                                ->body($e->getMessage())
   112	                                ->danger()
   113	                                ->send();
   114	                        }
   115	                    }),
   116	            ]);
   117	    }
   118	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [145]: app/Filament/Resources/MemberResource/RelationManagers/SubscriptionsRelationManager.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [146/494]: app/Filament/Resources/MemberResource/RelationManagers/ViolationsRelationManager.php
│ LANGUAGE: php | LINES: 76 | SIZE: 2868 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Filament\Resources\MemberResource\RelationManagers;
     4	
     5	use App\Enums\ViolationSeverity;
     6	use App\Enums\ViolationStatus;
     7	use Filament\Resources\RelationManagers\RelationManager;
     8	use Filament\Tables;
     9	use Filament\Tables\Table;
    10	use Filament\Support\Enums\FontWeight;
    11	
    12	class ViolationsRelationManager extends RelationManager
    13	{
    14	    protected static string $relationship = 'violations';
    15	
    16	    protected static ?string $title = 'المخالفات';
    17	
    18	    protected static ?string $modelLabel = 'مخالفة';
    19	
    20	    protected static ?string $pluralModelLabel = 'المخالفات';
    21	
    22	    protected static ?string $icon = 'heroicon-o-exclamation-triangle';
    23	
    24	    public function table(Table $table): Table
    25	    {
    26	        return $table
    27	            ->recordTitleAttribute('violation_number')
    28	            ->columns([
    29	                Tables\Columns\TextColumn::make('violation_number')
    30	                    ->label('رقم المخالفة')
    31	                    ->searchable()
    32	                    ->sortable()
    33	                    ->weight(FontWeight::Bold)
    34	                    ->copyable(),
    35	
    36	                Tables\Columns\TextColumn::make('violationType.name_ar')
    37	                    ->label('نوع المخالفة')
    38	                    ->sortable(),
    39	
    40	                Tables\Columns\TextColumn::make('severity')
    41	                    ->label('الخطورة')
    42	                    ->badge()
    43	                    ->color(fn(ViolationSeverity $state) => $state->getColor()),
    44	
    45	                Tables\Columns\TextColumn::make('status')
    46	                    ->label('الحالة')
    47	                    ->badge()
    48	                    ->color(fn(ViolationStatus $state) => $state->getColor()),
    49	
    50	                Tables\Columns\TextColumn::make('violation_date')
    51	                    ->label('التاريخ')
    52	                    ->date('Y-m-d')
    53	                    ->sortable(),
    54	
    55	                Tables\Columns\TextColumn::make('description')
    56	                    ->label('الوصف')
    57	                    ->limit(40)
    58	                    ->tooltip(fn($record) => $record->description),
    59	            ])
    60	            ->defaultSort('violation_date', 'desc')
    61	            ->filters([
    62	                Tables\Filters\SelectFilter::make('severity')
    63	                    ->label('الخطورة')
    64	                    ->options(ViolationSeverity::toFilamentOptions()),
    65	
    66	                Tables\Filters\SelectFilter::make('status')
    67	                    ->label('الحالة')
    68	                    ->options(ViolationStatus::toFilamentOptions()),
    69	            ])
    70	            ->actions([
    71	                Tables\Actions\ViewAction::make()->label('عرض'),
    72	            ])
    73	            ->emptyStateHeading('لا توجد مخالفات')
    74	            ->emptyStateDescription('لم تُسجل أي مخالفات على هذا العضو')
    75	            ->emptyStateIcon('heroicon-o-shield-check');
    76	    }
    77	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [146]: app/Filament/Resources/MemberResource/RelationManagers/ViolationsRelationManager.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [147/494]: app/Filament/Resources/MemberSuspensionResource.php
│ LANGUAGE: php | LINES: 379 | SIZE: 16461 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	declare(strict_types=1);
     4	
     5	namespace App\Filament\Resources;
     6	
     7	use App\Enums\SuspensionStatus;
     8	use App\Filament\Resources\MemberSuspensionResource\Pages;
     9	use App\Models\MemberSuspension;
    10	use App\Services\Disciplinary\SuspensionService;
    11	use Carbon\Carbon;
    12	use Filament\Forms;
    13	use Filament\Forms\Form;
    14	use Filament\Infolists;
    15	use Filament\Infolists\Infolist;
    16	use Filament\Resources\Resource;
    17	use Filament\Tables;
    18	use Filament\Tables\Table;
    19	use Illuminate\Database\Eloquent\Builder;
    20	
    21	class MemberSuspensionResource extends Resource
    22	{
    23	    protected static ?string $model = MemberSuspension::class;
    24	
    25	    protected static ?string $navigationIcon = 'heroicon-o-no-symbol';
    26	
    27	    protected static ?string $navigationGroup = 'الانضباط';
    28	
    29	    protected static ?string $navigationLabel = 'إيقاف العضوية';
    30	
    31	    protected static ?string $modelLabel = 'إيقاف عضوية';
    32	
    33	    protected static ?string $pluralModelLabel = 'إيقافات العضوية';
    34	
    35	    protected static ?int $navigationSort = 3;
    36	
    37	    public static function getNavigationBadge(): ?string
    38	    {
    39	        return (string) static::getModel()::where('status', SuspensionStatus::ACTIVE)->count() ?: null;
    40	    }
    41	
    42	    public static function getNavigationBadgeColor(): string|array|null
    43	    {
    44	        return 'danger';
    45	    }
    46	
    47	    public static function form(Form $form): Form
    48	    {
    49	        return $form->schema([
    50	            Forms\Components\Section::make('بيانات الإيقاف')
    51	                ->schema([
    52	                    Forms\Components\Select::make('member_id')
    53	                        ->label('العضو')
    54	                        ->relationship('member', 'full_name_ar')
    55	                        ->searchable()
    56	                        ->preload()
    57	                        ->required()
    58	                        ->getSearchResultsUsing(function (string $search): array {
    59	                            return \App\Models\Member::query()
    60	                                ->where('full_name_ar', 'like', "%{$search}%")
    61	                                ->orWhere('membership_number', 'like', "%{$search}%")
    62	                                ->limit(50)
    63	                                ->pluck('full_name_ar', 'id')
    64	                                ->toArray();
    65	                        }),
    66	
    67	                    Forms\Components\Select::make('violation_id')
    68	                        ->label('المخالفة (اختياري)')
    69	                        ->relationship('violation', 'violation_number')
    70	                        ->searchable()
    71	                        ->preload(),
    72	
    73	                    Forms\Components\Select::make('penalty_id')
    74	                        ->label('العقوبة (اختياري)')
    75	                        ->relationship('penalty', 'penalty_number')
    76	                        ->searchable()
    77	                        ->preload(),
    78	
    79	                    Forms\Components\DatePicker::make('start_date')
    80	                        ->label('تاريخ البدء')
    81	                        ->required()
    82	                        ->default(now()),
    83	
    84	                    Forms\Components\DatePicker::make('end_date')
    85	                        ->label('تاريخ الانتهاء')
    86	                        ->after('start_date'),
    87	
    88	                    Forms\Components\Textarea::make('reason')
    89	                        ->label('سبب الإيقاف')
    90	                        ->required()
    91	                        ->rows(3)
    92	                        ->columnSpanFull(),
    93	
    94	                    Forms\Components\TextInput::make('board_decision_reference')
    95	                        ->label('رقم قرار المجلس'),
    96	
    97	                    Forms\Components\Textarea::make('notes')
    98	                        ->label('ملاحظات')
    99	                        ->rows(2)
   100	                        ->columnSpanFull(),
   101	                ])
   102	                ->columns(2),
   103	        ]);
   104	    }
   105	
   106	    public static function infolist(Infolist $infolist): Infolist
   107	    {
   108	        return $infolist->schema([
   109	            Infolists\Components\Section::make('بيانات الإيقاف')
   110	                ->schema([
   111	                    Infolists\Components\TextEntry::make('member.full_name_ar')
   112	                        ->label('العضو'),
   113	
   114	                    Infolists\Components\TextEntry::make('member.membership_number')
   115	                        ->label('رقم العضوية'),
   116	
   117	                    Infolists\Components\TextEntry::make('status')
   118	                        ->label('الحالة')
   119	                        ->badge()
   120	                        ->formatStateUsing(fn ($state) => $state instanceof SuspensionStatus ? $state->getLabel() : $state)
   121	                        ->color(fn ($state) => $state instanceof SuspensionStatus ? $state->getColor() : 'gray'),
   122	
   123	                    Infolists\Components\TextEntry::make('reason')
   124	                        ->label('السبب')
   125	                        ->columnSpanFull(),
   126	                ])
   127	                ->columns(2),
   128	
   129	            Infolists\Components\Section::make('المدة')
   130	                ->schema([
   131	                    Infolists\Components\TextEntry::make('start_date')
   132	                        ->label('تاريخ البدء')
   133	                        ->date('d/m/Y'),
   134	
   135	                    Infolists\Components\TextEntry::make('end_date')
   136	                        ->label('تاريخ الانتهاء')
   137	                        ->date('d/m/Y')
   138	                        ->default('غير محدد'),
   139	
   140	                    Infolists\Components\TextEntry::make('actual_end_date')
   141	                        ->label('تاريخ الانتهاء الفعلي')
   142	                        ->date('d/m/Y')
   143	                        ->default('—'),
   144	
   145	                    Infolists\Components\TextEntry::make('remaining_days')
   146	                        ->label('الأيام المتبقية')
   147	                        ->state(function (MemberSuspension $record): string {
   148	                            if ($record->status !== SuspensionStatus::ACTIVE || !$record->end_date) {
   149	                                return '—';
   150	                            }
   151	                            $days = now()->diffInDays(Carbon::parse($record->end_date), false);
   152	                            return $days > 0 ? "{$days} يوم" : 'منتهية';
   153	                        })
   154	                        ->badge()
   155	                        ->color(function (MemberSuspension $record): string {
   156	                            if ($record->status !== SuspensionStatus::ACTIVE || !$record->end_date) {
   157	                                return 'gray';
   158	                            }
   159	                            $days = now()->diffInDays(Carbon::parse($record->end_date), false);
   160	                            return $days <= 3 ? 'danger' : ($days <= 7 ? 'warning' : 'success');
   161	                        }),
   162	                ])
   163	                ->columns(2),
   164	
   165	            Infolists\Components\Section::make('الروابط')
   166	                ->schema([
   167	                    Infolists\Components\TextEntry::make('violation.violation_number')
   168	                        ->label('المخالفة')
   169	                        ->url(fn (MemberSuspension $record) => $record->violation_id
   170	                            ? ViolationResource::getUrl('view', ['record' => $record->violation_id])
   171	                            : null)
   172	                        ->default('—'),
   173	
   174	                    Infolists\Components\TextEntry::make('penalty.penalty_number')
   175	                        ->label('العقوبة')
   176	                        ->url(fn (MemberSuspension $record) => $record->penalty_id
   177	                            ? PenaltyResource::getUrl('view', ['record' => $record->penalty_id])
   178	                            : null)
   179	                        ->default('—'),
   180	
   181	                    Infolists\Components\TextEntry::make('board_decision_reference')
   182	                        ->label('رقم قرار المجلس')
   183	                        ->default('—'),
   184	                ])
   185	                ->columns(2),
   186	
   187	            Infolists\Components\Section::make('المعلومات الإدارية')
   188	                ->schema([
   189	                    Infolists\Components\TextEntry::make('suspendedBy.name')
   190	                        ->label('أُوقف بواسطة'),
   191	
   192	                    Infolists\Components\TextEntry::make('created_at')
   193	                        ->label('تاريخ الإنشاء')
   194	                        ->dateTime('d/m/Y H:i'),
   195	
   196	                    Infolists\Components\TextEntry::make('liftedBy.name')
   197	                        ->label('رُفع بواسطة')
   198	                        ->default('—'),
   199	
   200	                    Infolists\Components\TextEntry::make('lifted_at')
   201	                        ->label('تاريخ الرفع')
   202	                        ->dateTime('d/m/Y H:i')
   203	                        ->default('—'),
   204	
   205	                    Infolists\Components\TextEntry::make('lift_reason')
   206	                        ->label('سبب الرفع')
   207	                        ->default('—')
   208	                        ->columnSpanFull(),
   209	
   210	                    Infolists\Components\TextEntry::make('notes')
   211	                        ->label('ملاحظات')
   212	                        ->default('—')
   213	                        ->columnSpanFull(),
   214	                ])
   215	                ->columns(2),
   216	        ]);
   217	    }
   218	
   219	    public static function table(Table $table): Table
   220	    {
   221	        return $table
   222	            ->columns([
   223	                Tables\Columns\TextColumn::make('member.full_name_ar')
   224	                    ->label('العضو')
   225	                    ->searchable()
   226	                    ->sortable(),
   227	
   228	                Tables\Columns\TextColumn::make('member.membership_number')
   229	                    ->label('رقم العضوية')
   230	                    ->searchable(),
   231	
   232	                Tables\Columns\TextColumn::make('status')
   233	                    ->label('الحالة')
   234	                    ->badge()
   235	                    ->formatStateUsing(fn ($state) => $state instanceof SuspensionStatus ? $state->getLabel() : $state)
   236	                    ->color(fn ($state) => $state instanceof SuspensionStatus ? $state->getColor() : 'gray')
   237	                    ->sortable(),
   238	
   239	                Tables\Columns\TextColumn::make('reason')
   240	                    ->label('السبب')
   241	                    ->limit(50)
   242	                    ->tooltip(fn ($record) => $record->reason),
   243	
   244	                Tables\Columns\TextColumn::make('start_date')
   245	                    ->label('من')
   246	                    ->date('d/m/Y')
   247	                    ->sortable(),
   248	
   249	                Tables\Columns\TextColumn::make('end_date')
   250	                    ->label('حتى')
   251	                    ->date('d/m/Y')
   252	                    ->default('غير محدد')
   253	                    ->sortable(),
   254	
   255	                Tables\Columns\TextColumn::make('remaining_days')
   256	                    ->label('المتبقي')
   257	                    ->state(function (MemberSuspension $record): string {
   258	                        if ($record->status !== SuspensionStatus::ACTIVE || !$record->end_date) {
   259	                            return '—';
   260	                        }
   261	                        $days = (int) now()->diffInDays(Carbon::parse($record->end_date), false);
   262	                        return $days > 0 ? "{$days} يوم" : 'منتهية';
   263	                    })
   264	                    ->badge()
   265	                    ->color(function (MemberSuspension $record): string {
   266	                        if ($record->status !== SuspensionStatus::ACTIVE || !$record->end_date) {
   267	                            return 'gray';
   268	                        }
   269	                        $days = (int) now()->diffInDays(Carbon::parse($record->end_date), false);
   270	                        return $days <= 3 ? 'danger' : ($days <= 7 ? 'warning' : 'info');
   271	                    }),
   272	
   273	                Tables\Columns\TextColumn::make('violation.violation_number')
   274	                    ->label('المخالفة')
   275	                    ->default('—'),
   276	
   277	                Tables\Columns\TextColumn::make('suspendedBy.name')
   278	                    ->label('بواسطة'),
   279	            ])
   280	            ->defaultSort('created_at', 'desc')
   281	            ->filters([
   282	                Tables\Filters\SelectFilter::make('status')
   283	                    ->label('الحالة')
   284	                    ->options(SuspensionStatus::toFilamentOptions())
   285	                    ->multiple(),
   286	
   287	                Tables\Filters\Filter::make('active_now')
   288	                    ->label('نشطة حالياً')
   289	                    ->query(fn (Builder $query) => $query
   290	                        ->where('status', SuspensionStatus::ACTIVE)
   291	                        ->where('start_date', '<=', now())
   292	                        ->where(fn ($q) => $q->whereNull('end_date')->orWhere('end_date', '>=', now()))
   293	                    )
   294	                    ->toggle(),
   295	
   296	                Tables\Filters\Filter::make('expiring_soon')
   297	                    ->label('تنتهي قريباً (7 أيام)')
   298	                    ->query(fn (Builder $query) => $query
   299	                        ->where('status', SuspensionStatus::ACTIVE)
   300	                        ->whereNotNull('end_date')
   301	                        ->whereBetween('end_date', [now(), now()->addDays(7)])
   302	                    )
   303	                    ->toggle(),
   304	
   305	                Tables\Filters\Filter::make('indefinite')
   306	                    ->label('غير محددة المدة')
   307	                    ->query(fn (Builder $query) => $query
   308	                        ->where('status', SuspensionStatus::ACTIVE)
   309	                        ->whereNull('end_date')
   310	                    )
   311	                    ->toggle(),
   312	            ])
   313	            ->actions([
   314	                Tables\Actions\ActionGroup::make([
   315	                    Tables\Actions\ViewAction::make(),
   316	
   317	                    Tables\Actions\Action::make('lift')
   318	                        ->label('رفع الإيقاف')
   319	                        ->icon('heroicon-o-arrow-up-circle')
   320	                        ->color('success')
   321	                        ->requiresConfirmation()
   322	                        ->modalHeading('رفع الإيقاف عن العضو')
   323	                        ->form([
   324	                            Forms\Components\Textarea::make('reason')
   325	                                ->label('سبب رفع الإيقاف')
   326	                                ->required()
   327	                                ->rows(3),
   328	                        ])
   329	                        ->action(fn (MemberSuspension $record, array $data) =>
   330	                            app(SuspensionService::class)->liftSuspension($record, $data)
   331	                        )
   332	                        ->visible(fn (MemberSuspension $record) => $record->status === SuspensionStatus::ACTIVE),
   333	
   334	                    Tables\Actions\Action::make('extend')
   335	                        ->label('تمديد الإيقاف')
   336	                        ->icon('heroicon-o-calendar-days')
   337	                        ->color('warning')
   338	                        ->form([
   339	                            Forms\Components\DatePicker::make('new_end_date')
   340	                                ->label('تاريخ الانتهاء الجديد')
   341	                                ->required()
   342	                                ->after('today'),
   343	
   344	                            Forms\Components\Textarea::make('reason')
   345	                                ->label('سبب التمديد')
   346	                                ->rows(3),
   347	                        ])
   348	                        ->action(fn (MemberSuspension $record, array $data) =>
   349	                            app(SuspensionService::class)->extendSuspension(
   350	                                $record,
   351	                                Carbon::parse($data['new_end_date']),
   352	                                $data['reason'] ?? null
   353	                            )
   354	                        )
   355	                        ->visible(fn (MemberSuspension $record) => $record->status === SuspensionStatus::ACTIVE),
   356	                ]),
   357	            ])
   358	            ->bulkActions([]);
   359	    }
   360	
   361	    public static function getRelations(): array
   362	    {
   363	        return [];
   364	    }
   365	
   366	    public static function getPages(): array
   367	    {
   368	        return [
   369	            'index'  => Pages\ListMemberSuspensions::route('/'),
   370	            'create' => Pages\CreateMemberSuspension::route('/create'),
   371	            'view'   => Pages\ViewMemberSuspension::route('/{record}'),
   372	        ];
   373	    }
   374	
   375	    public static function getEloquentQuery(): Builder
   376	    {
   377	        return parent::getEloquentQuery()
   378	            ->with(['member', 'violation', 'penalty', 'suspendedBy']);
   379	    }
   380	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [147]: app/Filament/Resources/MemberSuspensionResource.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [148/494]: app/Filament/Resources/MemberSuspensionResource/Pages/CreateMemberSuspension.php
│ LANGUAGE: php | LINES: 23 | SIZE: 692 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	declare(strict_types=1);
     4	
     5	namespace App\Filament\Resources\MemberSuspensionResource\Pages;
     6	
     7	use App\Filament\Resources\MemberSuspensionResource;
     8	use App\Services\Disciplinary\SuspensionService;
     9	use Filament\Resources\Pages\CreateRecord;
    10	
    11	class CreateMemberSuspension extends CreateRecord
    12	{
    13	    protected static string $resource = MemberSuspensionResource::class;
    14	
    15	    protected function handleRecordCreation(array $data): \Illuminate\Database\Eloquent\Model
    16	    {
    17	        return app(SuspensionService::class)->createSuspension($data);
    18	    }
    19	
    20	    protected function getRedirectUrl(): string
    21	    {
    22	        return $this->getResource()::getUrl('view', ['record' => $this->record]);
    23	    }
    24	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [148]: app/Filament/Resources/MemberSuspensionResource/Pages/CreateMemberSuspension.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [149/494]: app/Filament/Resources/MemberSuspensionResource/Pages/ListMemberSuspensions.php
│ LANGUAGE: php | LINES: 46 | SIZE: 1582 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	declare(strict_types=1);
     4	
     5	namespace App\Filament\Resources\MemberSuspensionResource\Pages;
     6	
     7	use App\Enums\SuspensionStatus;
     8	use App\Filament\Resources\MemberSuspensionResource;
     9	use Filament\Actions;
    10	use Filament\Resources\Components\Tab;
    11	use Filament\Resources\Pages\ListRecords;
    12	use Illuminate\Database\Eloquent\Builder;
    13	
    14	class ListMemberSuspensions extends ListRecords
    15	{
    16	    protected static string $resource = MemberSuspensionResource::class;
    17	
    18	    protected function getHeaderActions(): array
    19	    {
    20	        return [
    21	            Actions\CreateAction::make()
    22	                ->label('إيقاف عضوية'),
    23	        ];
    24	    }
    25	
    26	    public function getTabs(): array
    27	    {
    28	        return [
    29	            'all' => Tab::make('الكل')
    30	                ->icon('heroicon-o-list-bullet'),
    31	
    32	            'active' => Tab::make('نشطة')
    33	                ->icon('heroicon-o-no-symbol')
    34	                ->modifyQueryUsing(fn (Builder $query) => $query->where('status', SuspensionStatus::ACTIVE))
    35	                ->badge(fn () => static::getModel()::where('status', SuspensionStatus::ACTIVE)->count() ?: null)
    36	                ->badgeColor('danger'),
    37	
    38	            'lifted' => Tab::make('مرفوعة')
    39	                ->icon('heroicon-o-arrow-up-circle')
    40	                ->modifyQueryUsing(fn (Builder $query) => $query->where('status', SuspensionStatus::LIFTED)),
    41	
    42	            'expired' => Tab::make('منتهية')
    43	                ->icon('heroicon-o-clock')
    44	                ->modifyQueryUsing(fn (Builder $query) => $query->where('status', SuspensionStatus::EXPIRED)),
    45	        ];
    46	    }
    47	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [149]: app/Filament/Resources/MemberSuspensionResource/Pages/ListMemberSuspensions.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [150/494]: app/Filament/Resources/MemberSuspensionResource/Pages/ViewMemberSuspension.php
│ LANGUAGE: php | LINES: 63 | SIZE: 2384 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	declare(strict_types=1);
     4	
     5	namespace App\Filament\Resources\MemberSuspensionResource\Pages;
     6	
     7	use App\Enums\SuspensionStatus;
     8	use App\Filament\Resources\MemberSuspensionResource;
     9	use App\Services\Disciplinary\SuspensionService;
    10	use Carbon\Carbon;
    11	use Filament\Actions;
    12	use Filament\Forms;
    13	use Filament\Resources\Pages\ViewRecord;
    14	
    15	class ViewMemberSuspension extends ViewRecord
    16	{
    17	    protected static string $resource = MemberSuspensionResource::class;
    18	
    19	    protected function getHeaderActions(): array
    20	    {
    21	        return [
    22	            Actions\Action::make('lift')
    23	                ->label('رفع الإيقاف')
    24	                ->icon('heroicon-o-arrow-up-circle')
    25	                ->color('success')
    26	                ->requiresConfirmation()
    27	                ->form([
    28	                    Forms\Components\Textarea::make('reason')
    29	                        ->label('سبب رفع الإيقاف')
    30	                        ->required()
    31	                        ->rows(3),
    32	                ])
    33	                ->action(function (array $data) {
    34	                    app(SuspensionService::class)->liftSuspension($this->record, $data);
    35	                    $this->refreshFormData(['status']);
    36	                })
    37	                ->visible(fn () => $this->record->status === SuspensionStatus::ACTIVE),
    38	
    39	            Actions\Action::make('extend')
    40	                ->label('تمديد الإيقاف')
    41	                ->icon('heroicon-o-calendar-days')
    42	                ->color('warning')
    43	                ->form([
    44	                    Forms\Components\DatePicker::make('new_end_date')
    45	                        ->label('تاريخ الانتهاء الجديد')
    46	                        ->required()
    47	                        ->after('today'),
    48	
    49	                    Forms\Components\Textarea::make('reason')
    50	                        ->label('سبب التمديد')
    51	                        ->rows(3),
    52	                ])
    53	                ->action(function (array $data) {
    54	                    app(SuspensionService::class)->extendSuspension(
    55	                        $this->record,
    56	                        Carbon::parse($data['new_end_date']),
    57	                        $data['reason'] ?? null
    58	                    );
    59	                    $this->refreshFormData(['end_date', 'status']);
    60	                })
    61	                ->visible(fn () => $this->record->status === SuspensionStatus::ACTIVE),
    62	        ];
    63	    }
    64	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [150]: app/Filament/Resources/MemberSuspensionResource/Pages/ViewMemberSuspension.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [151/494]: app/Filament/Resources/NotificationResource.php
│ LANGUAGE: php | LINES: 278 | SIZE: 11648 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Filament\Resources;
     4	
     5	use App\Filament\Resources\NotificationResource\Pages;
     6	use App\Models\Notification;
     7	use App\Enums\NotificationChannel;
     8	use App\Enums\NotificationPriority;
     9	use Filament\Forms;
    10	use Filament\Forms\Form;
    11	use Filament\Resources\Resource;
    12	use Filament\Tables;
    13	use Filament\Tables\Table;
    14	use Filament\Infolists;
    15	use Filament\Infolists\Infolist;
    16	use Illuminate\Database\Eloquent\Builder;
    17	use Filament\Notifications\Notification as FilamentNotification;
    18	
    19	class NotificationResource extends Resource
    20	{
    21	    protected static ?string $model = Notification::class;
    22	
    23	    protected static ?string $navigationIcon = 'heroicon-o-bell';
    24	
    25	    protected static ?string $navigationGroup = 'إدارة النظام';
    26	
    27	    protected static ?string $navigationLabel = 'الإشعارات';
    28	
    29	    protected static ?string $modelLabel = 'إشعار';
    30	
    31	    protected static ?string $pluralModelLabel = 'الإشعارات';
    32	
    33	    protected static ?int $navigationSort = 4;
    34	
    35	    public static function form(Form $form): Form
    36	    {
    37	        return $form->schema([
    38	            Forms\Components\Section::make('بيانات الإشعار')
    39	                ->schema([
    40	                    Forms\Components\Select::make('channel')
    41	                        ->label('القناة')
    42	                        ->options(NotificationChannel::toFilamentOptions())
    43	                        ->default(NotificationChannel::SYSTEM->value)
    44	                        ->required(),
    45	
    46	                    Forms\Components\Select::make('priority')
    47	                        ->label('الأولوية')
    48	                        ->options(NotificationPriority::toFilamentOptions())
    49	                        ->default(NotificationPriority::NORMAL->value)
    50	                        ->required(),
    51	
    52	                    Forms\Components\TextInput::make('title_ar')
    53	                        ->label('العنوان (عربي)')
    54	                        ->required()
    55	                        ->maxLength(500),
    56	
    57	                    Forms\Components\TextInput::make('title_en')
    58	                        ->label('العنوان (إنجليزي)')
    59	                        ->maxLength(500),
    60	
    61	                    Forms\Components\Textarea::make('body_ar')
    62	                        ->label('المحتوى (عربي)')
    63	                        ->required()
    64	                        ->rows(4)
    65	                        ->columnSpanFull(),
    66	
    67	                    Forms\Components\Textarea::make('body_en')
    68	                        ->label('المحتوى (إنجليزي)')
    69	                        ->rows(4)
    70	                        ->columnSpanFull(),
    71	
    72	                    Forms\Components\TextInput::make('category')
    73	                        ->label('التصنيف')
    74	                        ->maxLength(100),
    75	
    76	                    Forms\Components\TextInput::make('action_url')
    77	                        ->label('رابط الإجراء')
    78	                        ->url()
    79	                        ->maxLength(500),
    80	                ])->columns(2),
    81	
    82	            Forms\Components\Section::make('المستلمون')
    83	                ->schema([
    84	                    Forms\Components\Select::make('target_type')
    85	                        ->label('نوع المستلم')
    86	                        ->options([
    87	                            'all_users' => 'جميع المستخدمين',
    88	                            'all_members' => 'جميع الأعضاء',
    89	                            'specific_users' => 'مستخدمين محددين',
    90	                            'specific_members' => 'أعضاء محددين',
    91	                        ])
    92	                        ->required()
    93	                        ->reactive()
    94	                        ->dehydrated(false),
    95	
    96	                    Forms\Components\Select::make('target_user_ids')
    97	                        ->label('المستخدمين')
    98	                        ->multiple()
    99	                        ->relationship('', 'name')
   100	                        ->options(\App\Models\User::pluck('name', 'id'))
   101	                        ->searchable()
   102	                        ->preload()
   103	                        ->visible(fn (Forms\Get $get) => $get('target_type') === 'specific_users')
   104	                        ->dehydrated(false),
   105	
   106	                    Forms\Components\Select::make('target_member_ids')
   107	                        ->label('الأعضاء')
   108	                        ->multiple()
   109	                        ->options(\App\Models\Member::pluck('full_name_ar', 'id'))
   110	                        ->searchable()
   111	                        ->visible(fn (Forms\Get $get) => $get('target_type') === 'specific_members')
   112	                        ->dehydrated(false),
   113	                ]),
   114	        ]);
   115	    }
   116	
   117	    public static function table(Table $table): Table
   118	    {
   119	        return $table
   120	            ->columns([
   121	                Tables\Columns\TextColumn::make('title_ar')
   122	                    ->label('العنوان')
   123	                    ->searchable()
   124	                    ->wrap()
   125	                    ->limit(60),
   126	
   127	                Tables\Columns\TextColumn::make('channel')
   128	                    ->label('القناة')
   129	                    ->badge()
   130	                    ->formatStateUsing(fn (NotificationChannel $state) => $state->getLabel())
   131	                    ->color(fn (NotificationChannel $state) => $state->getColor()),
   132	
   133	                Tables\Columns\TextColumn::make('priority')
   134	                    ->label('الأولوية')
   135	                    ->badge()
   136	                    ->formatStateUsing(fn (NotificationPriority $state) => $state->getLabel())
   137	                    ->color(fn (NotificationPriority $state) => $state->getColor()),
   138	
   139	                Tables\Columns\TextColumn::make('category')
   140	                    ->label('التصنيف')
   141	                    ->badge()
   142	                    ->toggleable(),
   143	
   144	                Tables\Columns\TextColumn::make('notifiable_type')
   145	                    ->label('نوع المستلم')
   146	                    ->formatStateUsing(fn (?string $state) => $state ? class_basename($state) : '—')
   147	                    ->badge(),
   148	
   149	                Tables\Columns\IconColumn::make('read_at')
   150	                    ->label('مقروء')
   151	                    ->boolean()
   152	                    ->trueIcon('heroicon-o-check-circle')
   153	                    ->falseIcon('heroicon-o-x-circle')
   154	                    ->getStateUsing(fn ($record) => $record->read_at !== null),
   155	
   156	                Tables\Columns\TextColumn::make('sent_at')
   157	                    ->label('تاريخ الإرسال')
   158	                    ->dateTime('d/m/Y H:i')
   159	                    ->sortable(),
   160	            ])
   161	            ->defaultSort('sent_at', 'desc')
   162	            ->filters([
   163	                Tables\Filters\SelectFilter::make('channel')
   164	                    ->label('القناة')
   165	                    ->options(NotificationChannel::toFilamentOptions()),
   166	
   167	                Tables\Filters\SelectFilter::make('priority')
   168	                    ->label('الأولوية')
   169	                    ->options(NotificationPriority::toFilamentOptions()),
   170	
   171	                Tables\Filters\TernaryFilter::make('read')
   172	                    ->label('الحالة')
   173	                    ->trueLabel('مقروء')
   174	                    ->falseLabel('غير مقروء')
   175	                    ->queries(
   176	                        true: fn (Builder $q) => $q->whereNotNull('read_at'),
   177	                        false: fn (Builder $q) => $q->whereNull('read_at'),
   178	                    ),
   179	
   180	                Tables\Filters\SelectFilter::make('category')
   181	                    ->label('التصنيف')
   182	                    ->options(fn () => Notification::distinct()->pluck('category', 'category')->filter()->toArray()),
   183	
   184	                Tables\Filters\Filter::make('date_range')
   185	                    ->label('فترة')
   186	                    ->form([
   187	                        Forms\Components\DatePicker::make('from')->label('من'),
   188	                        Forms\Components\DatePicker::make('to')->label('إلى'),
   189	                    ])
   190	                    ->query(function (Builder $query, array $data) {
   191	                        return $query
   192	                            ->when($data['from'], fn ($q, $d) => $q->whereDate('sent_at', '>=', $d))
   193	                            ->when($data['to'], fn ($q, $d) => $q->whereDate('sent_at', '<=', $d));
   194	                    }),
   195	            ])
   196	            ->actions([
   197	                Tables\Actions\ViewAction::make(),
   198	                Tables\Actions\DeleteAction::make(),
   199	            ])
   200	            ->bulkActions([
   201	                Tables\Actions\BulkActionGroup::make([
   202	                    Tables\Actions\DeleteBulkAction::make(),
   203	
   204	                    Tables\Actions\BulkAction::make('markAsRead')
   205	                        ->label('تحديد كمقروء')
   206	                        ->icon('heroicon-o-check')
   207	                        ->action(fn ($records) => $records->each->update(['read_at' => now()]))
   208	                        ->deselectRecordsAfterCompletion(),
   209	                ]),
   210	            ]);
   211	    }
   212	
   213	    public static function infolist(Infolist $infolist): Infolist
   214	    {
   215	        return $infolist->schema([
   216	            Infolists\Components\Section::make('تفاصيل الإشعار')
   217	                ->schema([
   218	                    Infolists\Components\TextEntry::make('title_ar')
   219	                        ->label('العنوان (عربي)'),
   220	
   221	                    Infolists\Components\TextEntry::make('title_en')
   222	                        ->label('العنوان (إنجليزي)')
   223	                        ->placeholder('—'),
   224	
   225	                    Infolists\Components\TextEntry::make('body_ar')
   226	                        ->label('المحتوى (عربي)')
   227	                        ->columnSpanFull(),
   228	
   229	                    Infolists\Components\TextEntry::make('body_en')
   230	                        ->label('المحتوى (إنجليزي)')
   231	                        ->columnSpanFull()
   232	                        ->placeholder('—'),
   233	
   234	                    Infolists\Components\TextEntry::make('channel')
   235	                        ->label('القناة')
   236	                        ->badge()
   237	                        ->formatStateUsing(fn (NotificationChannel $state) => $state->getLabel()),
   238	
   239	                    Infolists\Components\TextEntry::make('priority')
   240	                        ->label('الأولوية')
   241	                        ->badge()
   242	                        ->formatStateUsing(fn (NotificationPriority $state) => $state->getLabel()),
   243	
   244	                    Infolists\Components\TextEntry::make('category')
   245	                        ->label('التصنيف')
   246	                        ->placeholder('—'),
   247	
   248	                    Infolists\Components\TextEntry::make('action_url')
   249	                        ->label('رابط الإجراء')
   250	                        ->url()
   251	                        ->placeholder('—'),
   252	
   253	                    Infolists\Components\TextEntry::make('sent_at')
   254	                        ->label('تاريخ الإرسال')
   255	                        ->dateTime('d/m/Y H:i'),
   256	
   257	                    Infolists\Components\TextEntry::make('read_at')
   258	                        ->label('تاريخ القراءة')
   259	                        ->dateTime('d/m/Y H:i')
   260	                        ->placeholder('غير مقروء'),
   261	                ])->columns(2),
   262	        ]);
   263	    }
   264	
   265	    public static function getPages(): array
   266	    {
   267	        return [
   268	            'index' => Pages\ListNotifications::route('/'),
   269	            'create' => Pages\CreateNotification::route('/create'),
   270	            'view' => Pages\ViewNotification::route('/{record}'),
   271	        ];
   272	    }
   273	
   274	    public static function getNavigationBadge(): ?string
   275	    {
   276	        // Unread notifications count
   277	        return null;
   278	    }
   279	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [151]: app/Filament/Resources/NotificationResource.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [152/494]: app/Filament/Resources/NotificationResource/Pages/CreateNotification.php
│ LANGUAGE: php | LINES: 67 | SIZE: 2592 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Filament\Resources\NotificationResource\Pages;
     4	
     5	use App\Filament\Resources\NotificationResource;
     6	use App\Services\Admin\NotificationDispatchService;
     7	use App\Enums\NotificationChannel;
     8	use App\Enums\NotificationPriority;
     9	use Filament\Resources\Pages\CreateRecord;
    10	use Filament\Notifications\Notification as FilamentNotification;
    11	
    12	class CreateNotification extends CreateRecord
    13	{
    14	    protected static string $resource = NotificationResource::class;
    15	
    16	    protected function handleRecordCreation(array $data): \Illuminate\Database\Eloquent\Model
    17	    {
    18	        $service = app(NotificationDispatchService::class);
    19	        $targetType = $data['target_type'] ?? 'all_users';
    20	
    21	        $notifiables = match ($targetType) {
    22	            'all_users' => \App\Models\User::all(),
    23	            'all_members' => \App\Models\Member::all(),
    24	            'specific_users' => \App\Models\User::whereIn('id', $data['target_user_ids'] ?? [])->get(),
    25	            'specific_members' => \App\Models\Member::whereIn('id', $data['target_member_ids'] ?? [])->get(),
    26	            default => collect(),
    27	        };
    28	
    29	        if ($notifiables->isEmpty()) {
    30	            FilamentNotification::make()->title('لا يوجد مستلمون')->danger()->send();
    31	            // Create a dummy record to satisfy the interface
    32	            return \App\Models\Notification::create([
    33	                'notifiable_type' => \App\Models\User::class,
    34	                'notifiable_id' => auth()->id(),
    35	                'channel' => $data['channel'] ?? NotificationChannel::SYSTEM,
    36	                'priority' => $data['priority'] ?? NotificationPriority::NORMAL,
    37	                'title_ar' => $data['title_ar'],
    38	                'body_ar' => $data['body_ar'],
    39	                'sent_at' => now(),
    40	            ]);
    41	        }
    42	
    43	        $count = $service->sendBulk(
    44	            $notifiables,
    45	            $data['title_ar'],
    46	            $data['body_ar'],
    47	            NotificationChannel::from($data['channel']),
    48	            NotificationPriority::from($data['priority']),
    49	            $data['title_en'] ?? null,
    50	            $data['body_en'] ?? null,
    51	            $data['action_url'] ?? null,
    52	            $data['category'] ?? null,
    53	        );
    54	
    55	        FilamentNotification::make()
    56	            ->title("تم إرسال {$count} إشعار بنجاح")
    57	            ->success()
    58	            ->send();
    59	
    60	        // Return the first notification for redirect purposes
    61	        return \App\Models\Notification::latest()->first();
    62	    }
    63	
    64	    protected function getRedirectUrl(): string
    65	    {
    66	        return $this->getResource()::getUrl('index');
    67	    }
    68	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [152]: app/Filament/Resources/NotificationResource/Pages/CreateNotification.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [153/494]: app/Filament/Resources/NotificationResource/Pages/ListNotifications.php
│ LANGUAGE: php | LINES: 19 | SIZE: 489 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Filament\Resources\NotificationResource\Pages;
     4	
     5	use App\Filament\Resources\NotificationResource;
     6	use Filament\Actions;
     7	use Filament\Resources\Pages\ListRecords;
     8	
     9	class ListNotifications extends ListRecords
    10	{
    11	    protected static string $resource = NotificationResource::class;
    12	
    13	    protected function getHeaderActions(): array
    14	    {
    15	        return [
    16	            Actions\CreateAction::make()
    17	                ->label('إرسال إشعار جديد'),
    18	        ];
    19	    }
    20	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [153]: app/Filament/Resources/NotificationResource/Pages/ListNotifications.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [154/494]: app/Filament/Resources/NotificationResource/Pages/ViewNotification.php
│ LANGUAGE: php | LINES: 18 | SIZE: 428 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Filament\Resources\NotificationResource\Pages;
     4	
     5	use App\Filament\Resources\NotificationResource;
     6	use Filament\Actions;
     7	use Filament\Resources\Pages\ViewRecord;
     8	
     9	class ViewNotification extends ViewRecord
    10	{
    11	    protected static string $resource = NotificationResource::class;
    12	
    13	    protected function getHeaderActions(): array
    14	    {
    15	        return [
    16	            Actions\DeleteAction::make(),
    17	        ];
    18	    }
    19	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [154]: app/Filament/Resources/NotificationResource/Pages/ViewNotification.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [155/494]: app/Filament/Resources/PenaltyResource.php
│ LANGUAGE: php | LINES: 380 | SIZE: 15612 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	declare(strict_types=1);
     4	
     5	namespace App\Filament\Resources;
     6	
     7	use App\Enums\PenaltyStatus;
     8	use App\Filament\Resources\PenaltyResource\Pages;
     9	use App\Models\Penalty;
    10	use App\Models\PenaltyType;
    11	use App\Services\Disciplinary\PenaltyService;
    12	use Filament\Forms;
    13	use Filament\Forms\Form;
    14	use Filament\Infolists;
    15	use Filament\Infolists\Infolist;
    16	use Filament\Resources\Resource;
    17	use Filament\Tables;
    18	use Filament\Tables\Table;
    19	use Illuminate\Database\Eloquent\Builder;
    20	
    21	class PenaltyResource extends Resource
    22	{
    23	    protected static ?string $model = Penalty::class;
    24	
    25	    protected static ?string $navigationIcon = 'heroicon-o-scale';
    26	
    27	    protected static ?string $navigationGroup = 'الانضباط';
    28	
    29	    protected static ?string $navigationLabel = 'العقوبات';
    30	
    31	    protected static ?string $modelLabel = 'عقوبة';
    32	
    33	    protected static ?string $pluralModelLabel = 'العقوبات';
    34	
    35	    protected static ?int $navigationSort = 2;
    36	
    37	    protected static ?string $recordTitleAttribute = 'penalty_number';
    38	
    39	    public static function getNavigationBadge(): ?string
    40	    {
    41	        return (string) static::getModel()::whereIn('status', [
    42	            PenaltyStatus::ACTIVE,
    43	            PenaltyStatus::ENFORCED,
    44	        ])->count() ?: null;
    45	    }
    46	
    47	    public static function getNavigationBadgeColor(): string|array|null
    48	    {
    49	        return 'warning';
    50	    }
    51	
    52	    public static function form(Form $form): Form
    53	    {
    54	        return $form->schema([
    55	            Forms\Components\Section::make('بيانات العقوبة')
    56	                ->schema([
    57	                    Forms\Components\TextInput::make('penalty_number')
    58	                        ->label('رقم العقوبة')
    59	                        ->disabled()
    60	                        ->visibleOn('edit'),
    61	
    62	                    Forms\Components\Select::make('member_id')
    63	                        ->label('العضو')
    64	                        ->relationship('member', 'full_name_ar')
    65	                        ->searchable()
    66	                        ->preload()
    67	                        ->required(),
    68	
    69	                    Forms\Components\Select::make('violation_id')
    70	                        ->label('المخالفة')
    71	                        ->relationship('violation', 'violation_number')
    72	                        ->searchable()
    73	                        ->preload()
    74	                        ->required(),
    75	
    76	                    Forms\Components\Select::make('penalty_type_id')
    77	                        ->label('نوع العقوبة')
    78	                        ->options(PenaltyType::where('is_active', true)->pluck('name_ar', 'id'))
    79	                        ->reactive()
    80	                        ->afterStateUpdated(function (Forms\Set $set, $state) {
    81	                            if ($state) {
    82	                                $type = PenaltyType::find($state);
    83	                                if ($type) {
    84	                                    $set('fine_amount', $type->default_fine_amount);
    85	                                    $set('requires_suspension', $type->requires_suspension);
    86	                                    $set('suspension_days', $type->default_suspension_days);
    87	                                }
    88	                            }
    89	                        }),
    90	
    91	                    Forms\Components\Textarea::make('description')
    92	                        ->label('وصف العقوبة')
    93	                        ->required()
    94	                        ->rows(3)
    95	                        ->columnSpanFull(),
    96	                ])
    97	                ->columns(2),
    98	
    99	            Forms\Components\Section::make('الغرامة والإيقاف')
   100	                ->schema([
   101	                    Forms\Components\TextInput::make('fine_amount')
   102	                        ->label('مبلغ الغرامة')
   103	                        ->numeric()
   104	                        ->prefix('ج.م')
   105	                        ->default(0),
   106	
   107	                    Forms\Components\DatePicker::make('effective_from')
   108	                        ->label('سارية من')
   109	                        ->default(now()),
   110	
   111	                    Forms\Components\DatePicker::make('effective_until')
   112	                        ->label('سارية حتى'),
   113	
   114	                    Forms\Components\Toggle::make('requires_suspension')
   115	                        ->label('يتطلب إيقاف')
   116	                        ->reactive(),
   117	
   118	                    Forms\Components\TextInput::make('suspension_days')
   119	                        ->label('أيام الإيقاف')
   120	                        ->numeric()
   121	                        ->visible(fn (Forms\Get $get) => $get('requires_suspension')),
   122	
   123	                    Forms\Components\TextInput::make('board_decision_reference')
   124	                        ->label('رقم قرار المجلس'),
   125	
   126	                    Forms\Components\Textarea::make('notes')
   127	                        ->label('ملاحظات')
   128	                        ->rows(2)
   129	                        ->columnSpanFull(),
   130	                ])
   131	                ->columns(2),
   132	        ]);
   133	    }
   134	
   135	    public static function infolist(Infolist $infolist): Infolist
   136	    {
   137	        return $infolist->schema([
   138	            Infolists\Components\Section::make('بيانات العقوبة')
   139	                ->schema([
   140	                    Infolists\Components\TextEntry::make('penalty_number')
   141	                        ->label('رقم العقوبة')
   142	                        ->badge()
   143	                        ->color('warning'),
   144	
   145	                    Infolists\Components\TextEntry::make('member.full_name_ar')
   146	                        ->label('العضو'),
   147	
   148	                    Infolists\Components\TextEntry::make('violation.violation_number')
   149	                        ->label('رقم المخالفة')
   150	                        ->url(fn (Penalty $record) => ViolationResource::getUrl('view', ['record' => $record->violation_id])),
   151	
   152	                    Infolists\Components\TextEntry::make('penaltyType.name_ar')
   153	                        ->label('نوع العقوبة')
   154	                        ->default('—'),
   155	
   156	                    Infolists\Components\TextEntry::make('status')
   157	                        ->label('الحالة')
   158	                        ->badge()
   159	                        ->formatStateUsing(fn ($state) => $state instanceof PenaltyStatus ? $state->getLabel() : $state)
   160	                        ->color(fn ($state) => $state instanceof PenaltyStatus ? $state->getColor() : 'gray'),
   161	
   162	                    Infolists\Components\TextEntry::make('description')
   163	                        ->label('الوصف')
   164	                        ->columnSpanFull(),
   165	                ])
   166	                ->columns(2),
   167	
   168	            Infolists\Components\Section::make('الغرامة')
   169	                ->schema([
   170	                    Infolists\Components\TextEntry::make('fine_amount')
   171	                        ->label('مبلغ الغرامة')
   172	                        ->money('EGP'),
   173	
   174	                    Infolists\Components\IconEntry::make('fine_paid')
   175	                        ->label('تم السداد')
   176	                        ->boolean(),
   177	
   178	                    Infolists\Components\TextEntry::make('fine_paid_at')
   179	                        ->label('تاريخ السداد')
   180	                        ->dateTime('d/m/Y H:i')
   181	                        ->default('—'),
   182	                ])
   183	                ->columns(3),
   184	
   185	            Infolists\Components\Section::make('المدة')
   186	                ->schema([
   187	                    Infolists\Components\TextEntry::make('effective_from')
   188	                        ->label('سارية من')
   189	                        ->date('d/m/Y'),
   190	
   191	                    Infolists\Components\TextEntry::make('effective_until')
   192	                        ->label('سارية حتى')
   193	                        ->date('d/m/Y')
   194	                        ->default('غير محدد'),
   195	
   196	                    Infolists\Components\IconEntry::make('requires_suspension')
   197	                        ->label('يتطلب إيقاف')
   198	                        ->boolean(),
   199	
   200	                    Infolists\Components\TextEntry::make('suspension_days')
   201	                        ->label('أيام الإيقاف')
   202	                        ->suffix(' يوم')
   203	                        ->default('—'),
   204	                ])
   205	                ->columns(2),
   206	
   207	            Infolists\Components\Section::make('المعلومات الإدارية')
   208	                ->schema([
   209	                    Infolists\Components\TextEntry::make('imposedBy.name')
   210	                        ->label('فُرضت بواسطة'),
   211	
   212	                    Infolists\Components\TextEntry::make('imposed_date')
   213	                        ->label('تاريخ الفرض')
   214	                        ->date('d/m/Y'),
   215	
   216	                    Infolists\Components\TextEntry::make('board_decision_reference')
   217	                        ->label('رقم قرار المجلس')
   218	                        ->default('—'),
   219	
   220	                    Infolists\Components\TextEntry::make('completed_at')
   221	                        ->label('تاريخ الإنهاء')
   222	                        ->dateTime('d/m/Y H:i')
   223	                        ->default('—'),
   224	
   225	                    Infolists\Components\TextEntry::make('notes')
   226	                        ->label('ملاحظات')
   227	                        ->default('—')
   228	                        ->columnSpanFull(),
   229	                ])
   230	                ->columns(2),
   231	        ]);
   232	    }
   233	
   234	    public static function table(Table $table): Table
   235	    {
   236	        return $table
   237	            ->columns([
   238	                Tables\Columns\TextColumn::make('penalty_number')
   239	                    ->label('رقم العقوبة')
   240	                    ->searchable()
   241	                    ->sortable()
   242	                    ->weight('bold'),
   243	
   244	                Tables\Columns\TextColumn::make('member.full_name_ar')
   245	                    ->label('العضو')
   246	                    ->searchable()
   247	                    ->sortable(),
   248	
   249	                Tables\Columns\TextColumn::make('member.membership_number')
   250	                    ->label('رقم العضوية')
   251	                    ->searchable(),
   252	
   253	                Tables\Columns\TextColumn::make('violation.violation_number')
   254	                    ->label('رقم المخالفة')
   255	                    ->sortable(),
   256	
   257	                Tables\Columns\TextColumn::make('penaltyType.name_ar')
   258	                    ->label('النوع')
   259	                    ->default('—'),
   260	
   261	                Tables\Columns\TextColumn::make('status')
   262	                    ->label('الحالة')
   263	                    ->badge()
   264	                    ->formatStateUsing(fn ($state) => $state instanceof PenaltyStatus ? $state->getLabel() : $state)
   265	                    ->color(fn ($state) => $state instanceof PenaltyStatus ? $state->getColor() : 'gray')
   266	                    ->sortable(),
   267	
   268	                Tables\Columns\TextColumn::make('fine_amount')
   269	                    ->label('الغرامة')
   270	                    ->money('EGP')
   271	                    ->sortable(),
   272	
   273	                Tables\Columns\IconColumn::make('fine_paid')
   274	                    ->label('مسددة')
   275	                    ->boolean(),
   276	
   277	                Tables\Columns\IconColumn::make('requires_suspension')
   278	                    ->label('إيقاف')
   279	                    ->boolean(),
   280	
   281	                Tables\Columns\TextColumn::make('effective_from')
   282	                    ->label('من')
   283	                    ->date('d/m/Y')
   284	                    ->sortable(),
   285	
   286	                Tables\Columns\TextColumn::make('effective_until')
   287	                    ->label('حتى')
   288	                    ->date('d/m/Y')
   289	                    ->default('—'),
   290	            ])
   291	            ->defaultSort('created_at', 'desc')
   292	            ->filters([
   293	                Tables\Filters\SelectFilter::make('status')
   294	                    ->label('الحالة')
   295	                    ->options(PenaltyStatus::toFilamentOptions())
   296	                    ->multiple(),
   297	
   298	                Tables\Filters\TernaryFilter::make('fine_paid')
   299	                    ->label('الغرامة مسددة'),
   300	
   301	                Tables\Filters\TernaryFilter::make('requires_suspension')
   302	                    ->label('يتطلب إيقاف'),
   303	
   304	                Tables\Filters\SelectFilter::make('penalty_type_id')
   305	                    ->label('نوع العقوبة')
   306	                    ->relationship('penaltyType', 'name_ar'),
   307	
   308	                Tables\Filters\Filter::make('has_unpaid_fine')
   309	                    ->label('غرامات غير مسددة')
   310	                    ->query(fn (Builder $query) => $query->where('fine_amount', '>', 0)->where('fine_paid', false))
   311	                    ->toggle(),
   312	            ])
   313	            ->actions([
   314	                Tables\Actions\ActionGroup::make([
   315	                    Tables\Actions\ViewAction::make(),
   316	
   317	                    Tables\Actions\Action::make('record_payment')
   318	                        ->label('تسجيل سداد')
   319	                        ->icon('heroicon-o-banknotes')
   320	                        ->color('success')
   321	                        ->requiresConfirmation()
   322	                        ->action(fn (Penalty $record) => app(PenaltyService::class)->recordFinePayment($record))
   323	                        ->visible(fn (Penalty $record) => $record->fine_amount > 0 && !$record->fine_paid),
   324	
   325	                    Tables\Actions\Action::make('waive')
   326	                        ->label('إعفاء')
   327	                        ->icon('heroicon-o-hand-raised')
   328	                        ->color('warning')
   329	                        ->requiresConfirmation()
   330	                        ->form([
   331	                            Forms\Components\Textarea::make('reason')
   332	                                ->label('سبب الإعفاء')
   333	                                ->required(),
   334	                            Forms\Components\TextInput::make('board_decision_reference')
   335	                                ->label('رقم قرار المجلس'),
   336	                        ])
   337	                        ->action(fn (Penalty $record, array $data) => app(PenaltyService::class)->waive($record, $data))
   338	                        ->visible(fn (Penalty $record) => in_array($record->status, [PenaltyStatus::ACTIVE, PenaltyStatus::ENFORCED])),
   339	
   340	                    Tables\Actions\Action::make('overturn')
   341	                        ->label('إلغاء (استئناف)')
   342	                        ->icon('heroicon-o-arrow-uturn-left')
   343	                        ->color('danger')
   344	                        ->requiresConfirmation()
   345	                        ->form([
   346	                            Forms\Components\Textarea::make('reason')
   347	                                ->label('سبب الإلغاء')
   348	                                ->required(),
   349	                        ])
   350	                        ->action(fn (Penalty $record, array $data) => app(PenaltyService::class)->overturn($record, $data))
   351	                        ->visible(fn (Penalty $record) => in_array($record->status, [PenaltyStatus::ACTIVE, PenaltyStatus::ENFORCED])),
   352	                ]),
   353	            ])
   354	            ->bulkActions([]);
   355	    }
   356	
   357	    public static function getRelations(): array
   358	    {
   359	        return [];
   360	    }
   361	
   362	    public static function getPages(): array
   363	    {
   364	        return [
   365	            'index'  => Pages\ListPenalties::route('/'),
   366	            'create' => Pages\CreatePenalty::route('/create'),
   367	            'view'   => Pages\ViewPenalty::route('/{record}'),
   368	        ];
   369	    }
   370	
   371	    public static function getEloquentQuery(): Builder
   372	    {
   373	        return parent::getEloquentQuery()
   374	            ->with(['member', 'violation', 'penaltyType', 'imposedBy']);
   375	    }
   376	
   377	    public static function getGloballySearchableAttributes(): array
   378	    {
   379	        return ['penalty_number', 'member.full_name_ar', 'member.membership_number'];
   380	    }
   381	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [155]: app/Filament/Resources/PenaltyResource.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [156/494]: app/Filament/Resources/PenaltyResource/Pages/CreatePenalty.php
│ LANGUAGE: php | LINES: 23 | SIZE: 647 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	declare(strict_types=1);
     4	
     5	namespace App\Filament\Resources\PenaltyResource\Pages;
     6	
     7	use App\Filament\Resources\PenaltyResource;
     8	use App\Services\Disciplinary\PenaltyService;
     9	use Filament\Resources\Pages\CreateRecord;
    10	
    11	class CreatePenalty extends CreateRecord
    12	{
    13	    protected static string $resource = PenaltyResource::class;
    14	
    15	    protected function handleRecordCreation(array $data): \Illuminate\Database\Eloquent\Model
    16	    {
    17	        return app(PenaltyService::class)->createPenalty($data);
    18	    }
    19	
    20	    protected function getRedirectUrl(): string
    21	    {
    22	        return $this->getResource()::getUrl('view', ['record' => $this->record]);
    23	    }
    24	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [156]: app/Filament/Resources/PenaltyResource/Pages/CreatePenalty.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [157/494]: app/Filament/Resources/PenaltyResource/Pages/ListPenalties.php
│ LANGUAGE: php | LINES: 56 | SIZE: 2228 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	declare(strict_types=1);
     4	
     5	namespace App\Filament\Resources\PenaltyResource\Pages;
     6	
     7	use App\Enums\PenaltyStatus;
     8	use App\Filament\Resources\PenaltyResource;
     9	use Filament\Actions;
    10	use Filament\Resources\Components\Tab;
    11	use Filament\Resources\Pages\ListRecords;
    12	use Illuminate\Database\Eloquent\Builder;
    13	
    14	class ListPenalties extends ListRecords
    15	{
    16	    protected static string $resource = PenaltyResource::class;
    17	
    18	    protected function getHeaderActions(): array
    19	    {
    20	        return [
    21	            Actions\CreateAction::make()
    22	                ->label('فرض عقوبة جديدة'),
    23	        ];
    24	    }
    25	
    26	    public function getTabs(): array
    27	    {
    28	        return [
    29	            'all' => Tab::make('الكل')
    30	                ->icon('heroicon-o-list-bullet'),
    31	
    32	            'active' => Tab::make('نشطة')
    33	                ->icon('heroicon-o-bolt')
    34	                ->modifyQueryUsing(fn (Builder $query) => $query->whereIn('status', [PenaltyStatus::ACTIVE, PenaltyStatus::ENFORCED]))
    35	                ->badge(fn () => static::getModel()::whereIn('status', [PenaltyStatus::ACTIVE, PenaltyStatus::ENFORCED])->count() ?: null)
    36	                ->badgeColor('danger'),
    37	
    38	            'unpaid_fines' => Tab::make('غرامات غير مسددة')
    39	                ->icon('heroicon-o-banknotes')
    40	                ->modifyQueryUsing(fn (Builder $query) => $query->where('fine_amount', '>', 0)->where('fine_paid', false))
    41	                ->badge(fn () => static::getModel()::where('fine_amount', '>', 0)->where('fine_paid', false)->count() ?: null)
    42	                ->badgeColor('warning'),
    43	
    44	            'completed' => Tab::make('مكتملة')
    45	                ->icon('heroicon-o-check-circle')
    46	                ->modifyQueryUsing(fn (Builder $query) => $query->where('status', PenaltyStatus::COMPLETED)),
    47	
    48	            'waived' => Tab::make('معفاة')
    49	                ->icon('heroicon-o-hand-raised')
    50	                ->modifyQueryUsing(fn (Builder $query) => $query->where('status', PenaltyStatus::WAIVED)),
    51	
    52	            'overturned' => Tab::make('ملغاة')
    53	                ->icon('heroicon-o-arrow-uturn-left')
    54	                ->modifyQueryUsing(fn (Builder $query) => $query->where('status', PenaltyStatus::OVERTURNED)),
    55	        ];
    56	    }
    57	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [157]: app/Filament/Resources/PenaltyResource/Pages/ListPenalties.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [158/494]: app/Filament/Resources/PenaltyResource/Pages/ViewPenalty.php
│ LANGUAGE: php | LINES: 92 | SIZE: 3782 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	declare(strict_types=1);
     4	
     5	namespace App\Filament\Resources\PenaltyResource\Pages;
     6	
     7	use App\Enums\PenaltyStatus;
     8	use App\Filament\Resources\PenaltyResource;
     9	use App\Models\Penalty;
    10	use App\Services\Disciplinary\PenaltyService;
    11	use Filament\Actions;
    12	use Filament\Forms;
    13	use Filament\Resources\Pages\ViewRecord;
    14	
    15	class ViewPenalty extends ViewRecord
    16	{
    17	    protected static string $resource = PenaltyResource::class;
    18	
    19	    protected function getHeaderActions(): array
    20	    {
    21	        return [
    22	            Actions\Action::make('record_payment')
    23	                ->label('تسجيل سداد الغرامة')
    24	                ->icon('heroicon-o-banknotes')
    25	                ->color('success')
    26	                ->requiresConfirmation()
    27	                ->action(function () {
    28	                    app(PenaltyService::class)->recordFinePayment($this->record);
    29	                    $this->refreshFormData(['fine_paid', 'status']);
    30	                })
    31	                ->visible(fn () => $this->record->fine_amount > 0 && !$this->record->fine_paid),
    32	
    33	            Actions\Action::make('enforce')
    34	                ->label('تنفيذ العقوبة')
    35	                ->icon('heroicon-o-shield-exclamation')
    36	                ->color('danger')
    37	                ->requiresConfirmation()
    38	                ->action(function () {
    39	                    app(PenaltyService::class)->enforce($this->record);
    40	                    $this->refreshFormData(['status']);
    41	                })
    42	                ->visible(fn () => $this->record->status === PenaltyStatus::ACTIVE),
    43	
    44	            Actions\Action::make('waive')
    45	                ->label('إعفاء')
    46	                ->icon('heroicon-o-hand-raised')
    47	                ->color('warning')
    48	                ->requiresConfirmation()
    49	                ->form([
    50	                    Forms\Components\Textarea::make('reason')
    51	                        ->label('سبب الإعفاء')
    52	                        ->required()
    53	                        ->rows(3),
    54	
    55	                    Forms\Components\TextInput::make('board_decision_reference')
    56	                        ->label('رقم قرار المجلس'),
    57	                ])
    58	                ->action(function (array $data) {
    59	                    app(PenaltyService::class)->waive($this->record, $data);
    60	                    $this->refreshFormData(['status']);
    61	                })
    62	                ->visible(fn () => in_array($this->record->status, [PenaltyStatus::ACTIVE, PenaltyStatus::ENFORCED])),
    63	
    64	            Actions\Action::make('overturn')
    65	                ->label('إلغاء (استئناف)')
    66	                ->icon('heroicon-o-arrow-uturn-left')
    67	                ->color('danger')
    68	                ->requiresConfirmation()
    69	                ->form([
    70	                    Forms\Components\Textarea::make('reason')
    71	                        ->label('سبب الإلغاء')
    72	                        ->required()
    73	                        ->rows(3),
    74	                ])
    75	                ->action(function (array $data) {
    76	                    app(PenaltyService::class)->overturn($this->record, $data);
    77	                    $this->refreshFormData(['status']);
    78	                })
    79	                ->visible(fn () => in_array($this->record->status, [PenaltyStatus::ACTIVE, PenaltyStatus::ENFORCED])),
    80	
    81	            Actions\Action::make('complete')
    82	                ->label('إنهاء العقوبة')
    83	                ->icon('heroicon-o-check-circle')
    84	                ->color('success')
    85	                ->requiresConfirmation()
    86	                ->action(function () {
    87	                    app(PenaltyService::class)->complete($this->record);
    88	                    $this->refreshFormData(['status']);
    89	                })
    90	                ->visible(fn () => in_array($this->record->status, [PenaltyStatus::ACTIVE, PenaltyStatus::ENFORCED])),
    91	        ];
    92	    }
    93	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [158]: app/Filament/Resources/PenaltyResource/Pages/ViewPenalty.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [159/494]: app/Filament/Resources/PenaltyTypeResource.php
│ LANGUAGE: php | LINES: 161 | SIZE: 5690 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	declare(strict_types=1);
     4	
     5	namespace App\Filament\Resources;
     6	
     7	use App\Filament\Resources\PenaltyTypeResource\Pages;
     8	use App\Models\PenaltyType;
     9	use Filament\Forms;
    10	use Filament\Forms\Form;
    11	use Filament\Resources\Resource;
    12	use Filament\Tables;
    13	use Filament\Tables\Table;
    14	
    15	class PenaltyTypeResource extends Resource
    16	{
    17	    protected static ?string $model = PenaltyType::class;
    18	
    19	    protected static ?string $navigationIcon = 'heroicon-o-scale';
    20	
    21	    protected static ?string $navigationGroup = 'الانضباط';
    22	
    23	    protected static ?string $navigationLabel = 'أنواع العقوبات';
    24	
    25	    protected static ?string $modelLabel = 'نوع عقوبة';
    26	
    27	    protected static ?string $pluralModelLabel = 'أنواع العقوبات';
    28	
    29	    protected static ?int $navigationSort = 5;
    30	
    31	    public static function form(Form $form): Form
    32	    {
    33	        return $form->schema([
    34	            Forms\Components\Section::make('بيانات نوع العقوبة')
    35	                ->schema([
    36	                    Forms\Components\TextInput::make('name_ar')
    37	                        ->label('الاسم بالعربية')
    38	                        ->required()
    39	                        ->maxLength(255),
    40	
    41	                    Forms\Components\TextInput::make('name_en')
    42	                        ->label('الاسم بالإنجليزية')
    43	                        ->maxLength(255),
    44	
    45	                    Forms\Components\TextInput::make('code')
    46	                        ->label('الكود')
    47	                        ->required()
    48	                        ->unique(ignoreRecord: true)
    49	                        ->maxLength(50),
    50	
    51	                    Forms\Components\Textarea::make('description')
    52	                        ->label('الوصف')
    53	                        ->rows(3)
    54	                        ->columnSpanFull(),
    55	
    56	                    Forms\Components\TextInput::make('default_fine_amount')
    57	                        ->label('مبلغ الغرامة الافتراضي')
    58	                        ->numeric()
    59	                        ->prefix('ج.م')
    60	                        ->default(0),
    61	
    62	                    Forms\Components\Toggle::make('requires_suspension')
    63	                        ->label('يتطلب إيقاف')
    64	                        ->default(false)
    65	                        ->reactive(),
    66	
    67	                    Forms\Components\TextInput::make('default_suspension_days')
    68	                        ->label('أيام الإيقاف الافتراضية')
    69	                        ->numeric()
    70	                        ->visible(fn (Forms\Get $get) => $get('requires_suspension'))
    71	                        ->default(0),
    72	
    73	                    Forms\Components\Toggle::make('requires_board_approval')
    74	                        ->label('يتطلب موافقة المجلس')
    75	                        ->default(false),
    76	
    77	                    Forms\Components\Toggle::make('is_active')
    78	                        ->label('نشط')
    79	                        ->default(true),
    80	
    81	                    Forms\Components\TextInput::make('display_order')
    82	                        ->label('ترتيب العرض')
    83	                        ->numeric()
    84	                        ->default(0),
    85	                ])
    86	                ->columns(2),
    87	        ]);
    88	    }
    89	
    90	    public static function table(Table $table): Table
    91	    {
    92	        return $table
    93	            ->columns([
    94	                Tables\Columns\TextColumn::make('code')
    95	                    ->label('الكود')
    96	                    ->searchable()
    97	                    ->sortable(),
    98	
    99	                Tables\Columns\TextColumn::make('name_ar')
   100	                    ->label('الاسم')
   101	                    ->searchable()
   102	                    ->sortable(),
   103	
   104	                Tables\Columns\TextColumn::make('default_fine_amount')
   105	                    ->label('الغرامة الافتراضية')
   106	                    ->money('EGP')
   107	                    ->sortable(),
   108	
   109	                Tables\Columns\IconColumn::make('requires_suspension')
   110	                    ->label('يتطلب إيقاف')
   111	                    ->boolean(),
   112	
   113	                Tables\Columns\TextColumn::make('default_suspension_days')
   114	                    ->label('أيام الإيقاف')
   115	                    ->suffix(' يوم')
   116	                    ->sortable(),
   117	
   118	                Tables\Columns\IconColumn::make('requires_board_approval')
   119	                    ->label('يتطلب مجلس')
   120	                    ->boolean(),
   121	
   122	                Tables\Columns\IconColumn::make('is_active')
   123	                    ->label('نشط')
   124	                    ->boolean(),
   125	
   126	                Tables\Columns\TextColumn::make('penalties_count')
   127	                    ->label('عدد العقوبات')
   128	                    ->counts('penalties')
   129	                    ->sortable(),
   130	            ])
   131	            ->defaultSort('display_order')
   132	            ->filters([
   133	                Tables\Filters\TernaryFilter::make('requires_suspension')
   134	                    ->label('يتطلب إيقاف'),
   135	
   136	                Tables\Filters\TernaryFilter::make('is_active')
   137	                    ->label('نشط'),
   138	            ])
   139	            ->actions([
   140	                Tables\Actions\EditAction::make(),
   141	            ])
   142	            ->bulkActions([
   143	                Tables\Actions\BulkActionGroup::make([
   144	                    Tables\Actions\DeleteBulkAction::make(),
   145	                ]),
   146	            ]);
   147	    }
   148	
   149	    public static function getRelations(): array
   150	    {
   151	        return [];
   152	    }
   153	
   154	    public static function getPages(): array
   155	    {
   156	        return [
   157	            'index'  => Pages\ListPenaltyTypes::route('/'),
   158	            'create' => Pages\CreatePenaltyType::route('/create'),
   159	            'edit'   => Pages\EditPenaltyType::route('/{record}/edit'),
   160	        ];
   161	    }
   162	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [159]: app/Filament/Resources/PenaltyTypeResource.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [160/494]: app/Filament/Resources/PenaltyTypeResource/Pages/CreatePenaltyType.php
│ LANGUAGE: php | LINES: 17 | SIZE: 417 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	declare(strict_types=1);
     4	
     5	namespace App\Filament\Resources\PenaltyTypeResource\Pages;
     6	
     7	use App\Filament\Resources\PenaltyTypeResource;
     8	use Filament\Resources\Pages\CreateRecord;
     9	
    10	class CreatePenaltyType extends CreateRecord
    11	{
    12	    protected static string $resource = PenaltyTypeResource::class;
    13	
    14	    protected function getRedirectUrl(): string
    15	    {
    16	        return $this->getResource()::getUrl('index');
    17	    }
    18	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [160]: app/Filament/Resources/PenaltyTypeResource/Pages/CreatePenaltyType.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [161/494]: app/Filament/Resources/PenaltyTypeResource/Pages/EditPenaltyType.php
│ LANGUAGE: php | LINES: 25 | SIZE: 565 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	declare(strict_types=1);
     4	
     5	namespace App\Filament\Resources\PenaltyTypeResource\Pages;
     6	
     7	use App\Filament\Resources\PenaltyTypeResource;
     8	use Filament\Actions;
     9	use Filament\Resources\Pages\EditRecord;
    10	
    11	class EditPenaltyType extends EditRecord
    12	{
    13	    protected static string $resource = PenaltyTypeResource::class;
    14	
    15	    protected function getHeaderActions(): array
    16	    {
    17	        return [
    18	            Actions\DeleteAction::make(),
    19	        ];
    20	    }
    21	
    22	    protected function getRedirectUrl(): string
    23	    {
    24	        return $this->getResource()::getUrl('index');
    25	    }
    26	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [161]: app/Filament/Resources/PenaltyTypeResource/Pages/EditPenaltyType.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [162/494]: app/Filament/Resources/PenaltyTypeResource/Pages/ListPenaltyTypes.php
│ LANGUAGE: php | LINES: 20 | SIZE: 453 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	declare(strict_types=1);
     4	
     5	namespace App\Filament\Resources\PenaltyTypeResource\Pages;
     6	
     7	use App\Filament\Resources\PenaltyTypeResource;
     8	use Filament\Actions;
     9	use Filament\Resources\Pages\ListRecords;
    10	
    11	class ListPenaltyTypes extends ListRecords
    12	{
    13	    protected static string $resource = PenaltyTypeResource::class;
    14	
    15	    protected function getHeaderActions(): array
    16	    {
    17	        return [
    18	            Actions\CreateAction::make(),
    19	        ];
    20	    }
    21	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [162]: app/Filament/Resources/PenaltyTypeResource/Pages/ListPenaltyTypes.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [163/494]: app/Filament/Resources/ReceiptResource.php
│ LANGUAGE: php | LINES: 492 | SIZE: 23996 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Filament\Resources;
     4	
     5	use App\Filament\Resources\ReceiptResource\Pages;
     6	use App\Filament\Resources\ReceiptResource\RelationManagers;
     7	use App\Models\Receipt;
     8	use App\Models\Member;
     9	use App\Enums\ReceiptStatus;
    10	use App\Enums\FeeCategory;
    11	use Filament\Forms;
    12	use Filament\Forms\Form;
    13	use Filament\Resources\Resource;
    14	use Filament\Tables;
    15	use Filament\Tables\Table;
    16	use Filament\Infolists;
    17	use Filament\Infolists\Infolist;
    18	use Filament\Support\Enums\FontWeight;
    19	use Illuminate\Database\Eloquent\Builder;
    20	
    21	class ReceiptResource extends Resource
    22	{
    23	    protected static ?string $model = Receipt::class;
    24	
    25	    protected static ?string $navigationIcon = 'heroicon-o-document-text';
    26	    protected static ?string $navigationGroup = 'المالية';
    27	    protected static ?string $navigationLabel = 'الإيصالات';
    28	    protected static ?string $modelLabel = 'إيصال';
    29	    protected static ?string $pluralModelLabel = 'الإيصالات';
    30	    protected static ?int $navigationSort = 1;
    31	
    32	    public static function form(Form $form): Form
    33	    {
    34	        return $form
    35	            ->schema([
    36	                Forms\Components\Section::make('بيانات الإيصال')
    37	                    ->schema([
    38	                        Forms\Components\Select::make('member_id')
    39	                            ->label('العضو')
    40	                            ->relationship('member', 'full_name_ar')
    41	                            ->searchable(['full_name_ar', 'full_name_en', 'membership_number', 'national_id'])
    42	                            ->getOptionLabelFromRecordUsing(fn(Member $record) => "{$record->membership_number} - {$record->full_name_ar}")
    43	                            ->preload()
    44	                            ->required()
    45	                            ->live()
    46	                            ->afterStateUpdated(function (Forms\Set $set, ?string $state) {
    47	                                if ($state) {
    48	                                    $member = Member::find($state);
    49	                                    if ($member) {
    50	                                        $set('member_info', "{$member->membershipType?->name_ar} | هاتف: {$member->phone_primary}");
    51	                                    }
    52	                                }
    53	                            }),
    54	
    55	                        Forms\Components\Placeholder::make('member_info')
    56	                            ->label('معلومات العضو')
    57	                            ->content(fn(Forms\Get $get) => $get('member_info') ?? '-')
    58	                            ->hidden(fn(Forms\Get $get) => !$get('member_id')),
    59	
    60	                        Forms\Components\Select::make('fee_category')
    61	                            ->label('تصنيف الرسم')
    62	                            ->options(FeeCategory::toFilamentOptions())
    63	                            ->required()
    64	                            ->native(false),
    65	
    66	                        Forms\Components\Select::make('subscription_id')
    67	                            ->label('الاشتراك')
    68	                            ->relationship(
    69	                                'subscription',
    70	                                'subscription_year',
    71	                                fn(Builder $query, Forms\Get $get) => $query
    72	                                    ->where('member_id', $get('member_id'))
    73	                                    ->where('is_archived', false)
    74	                                    ->whereIn('status', ['due', 'partially_paid', 'overdue'])
    75	                            )
    76	                            ->getOptionLabelFromRecordUsing(fn($record) => "سنة {$record->subscription_year} - متبقي: {$record->amount_remaining} جنيه")
    77	                            ->hidden(fn(Forms\Get $get) => !$get('member_id'))
    78	                            ->nullable(),
    79	
    80	                        Forms\Components\DatePicker::make('receipt_date')
    81	                            ->label('تاريخ الإيصال')
    82	                            ->default(now())
    83	                            ->required(),
    84	
    85	                        Forms\Components\Select::make('payment_method')
    86	                            ->label('طريقة الدفع')
    87	                            ->options([
    88	                                'cash' => 'نقدي',
    89	                                'bank_transfer' => 'تحويل بنكي',
    90	                                'check' => 'شيك',
    91	                                'credit_card' => 'بطاقة ائتمان',
    92	                                'pos' => 'نقطة بيع',
    93	                            ])
    94	                            ->default('cash')
    95	                            ->required()
    96	                            ->native(false),
    97	
    98	                        Forms\Components\TextInput::make('payment_reference')
    99	                            ->label('مرجع الدفع')
   100	                            ->placeholder('رقم الشيك / رقم التحويل')
   101	                            ->hidden(fn(Forms\Get $get) => $get('payment_method') === 'cash'),
   102	                    ])->columns(2),
   103	
   104	                Forms\Components\Section::make('بنود الإيصال')
   105	                    ->schema([
   106	                        Forms\Components\Repeater::make('items')
   107	                            ->label('البنود')
   108	                            ->schema([
   109	                                Forms\Components\Select::make('category')
   110	                                    ->label('التصنيف')
   111	                                    ->options(FeeCategory::toFilamentOptions())
   112	                                    ->required()
   113	                                    ->native(false)
   114	                                    ->columnSpan(2),
   115	
   116	                                Forms\Components\TextInput::make('description_ar')
   117	                                    ->label('الوصف بالعربي')
   118	                                    ->required()
   119	                                    ->columnSpan(3),
   120	
   121	                                Forms\Components\TextInput::make('description_en')
   122	                                    ->label('الوصف بالإنجليزي')
   123	                                    ->columnSpan(3),
   124	
   125	                                Forms\Components\TextInput::make('amount')
   126	                                    ->label('المبلغ')
   127	                                    ->numeric()
   128	                                    ->required()
   129	                                    ->live(onBlur: true)
   130	                                    ->columnSpan(2),
   131	
   132	                                Forms\Components\TextInput::make('quantity')
   133	                                    ->label('العدد')
   134	                                    ->numeric()
   135	                                    ->default(1)
   136	                                    ->minValue(1)
   137	                                    ->required()
   138	                                    ->live(onBlur: true)
   139	                                    ->columnSpan(1),
   140	
   141	                                Forms\Components\Placeholder::make('line_total')
   142	                                    ->label('الإجمالي')
   143	                                    ->content(function (Forms\Get $get) {
   144	                                        $amount = (float) ($get('amount') ?? 0);
   145	                                        $quantity = (int) ($get('quantity') ?? 1);
   146	                                        return number_format($amount * $quantity, 2) . ' جنيه';
   147	                                    })
   148	                                    ->columnSpan(1),
   149	                            ])
   150	                            ->columns(12)
   151	                            ->defaultItems(1)
   152	                            ->addActionLabel('إضافة بند')
   153	                            ->reorderable()
   154	                            ->collapsible(),
   155	                    ]),
   156	
   157	                Forms\Components\Section::make('ملخص المبالغ')
   158	                    ->schema([
   159	                        Forms\Components\TextInput::make('discount_amount')
   160	                            ->label('الخصم')
   161	                            ->numeric()
   162	                            ->default(0)
   163	                            ->live(onBlur: true),
   164	
   165	                        Forms\Components\TextInput::make('discount_reason')
   166	                            ->label('سبب الخصم')
   167	                            ->hidden(fn(Forms\Get $get) => (float) ($get('discount_amount') ?? 0) <= 0),
   168	
   169	                        Forms\Components\TextInput::make('amount_paid')
   170	                            ->label('المبلغ المدفوع')
   171	                            ->numeric()
   172	                            ->required()
   173	                            ->live(onBlur: true),
   174	
   175	                        Forms\Components\Textarea::make('notes_ar')
   176	                            ->label('ملاحظات')
   177	                            ->rows(2)
   178	                            ->columnSpanFull(),
   179	                    ])->columns(2),
   180	            ]);
   181	    }
   182	
   183	    public static function table(Table $table): Table
   184	    {
   185	        return $table
   186	            ->columns([
   187	                Tables\Columns\TextColumn::make('receipt_number')
   188	                    ->label('رقم الإيصال')
   189	                    ->searchable()
   190	                    ->sortable()
   191	                    ->weight(FontWeight::Bold)
   192	                    ->copyable(),
   193	
   194	                Tables\Columns\TextColumn::make('member.membership_number')
   195	                    ->label('رقم العضوية')
   196	                    ->searchable()
   197	                    ->sortable(),
   198	
   199	                Tables\Columns\TextColumn::make('member.full_name_ar')
   200	                    ->label('اسم العضو')
   201	                    ->searchable()
   202	                    ->sortable()
   203	                    ->limit(30),
   204	
   205	                Tables\Columns\TextColumn::make('fee_category')
   206	                    ->label('التصنيف')
   207	                    ->badge()
   208	                    ->formatStateUsing(fn(string $state) => FeeCategory::tryFrom($state)?->getLabel() ?? $state),
   209	
   210	                Tables\Columns\TextColumn::make('receipt_date')
   211	                    ->label('التاريخ')
   212	                    ->date('Y-m-d')
   213	                    ->sortable(),
   214	
   215	                Tables\Columns\TextColumn::make('total_amount')
   216	                    ->label('الإجمالي')
   217	                    ->money('EGP')
   218	                    ->sortable()
   219	                    ->weight(FontWeight::Bold),
   220	
   221	                Tables\Columns\TextColumn::make('amount_paid')
   222	                    ->label('المدفوع')
   223	                    ->money('EGP')
   224	                    ->sortable()
   225	                    ->color('success'),
   226	
   227	                Tables\Columns\TextColumn::make('amount_remaining')
   228	                    ->label('المتبقي')
   229	                    ->money('EGP')
   230	                    ->sortable()
   231	                    ->color(fn(string $state) => (float) $state > 0 ? 'danger' : 'success'),
   232	
   233	                Tables\Columns\TextColumn::make('payment_method')
   234	                    ->label('طريقة الدفع')
   235	                    ->badge()
   236	                    ->formatStateUsing(fn(string $state) => match ($state) {
   237	                        'cash' => 'نقدي',
   238	                        'bank_transfer' => 'تحويل بنكي',
   239	                        'check' => 'شيك',
   240	                        'credit_card' => 'بطاقة ائتمان',
   241	                        'pos' => 'نقطة بيع',
   242	                        default => $state,
   243	                    }),
   244	
   245	                Tables\Columns\TextColumn::make('status')
   246	                    ->label('الحالة')
   247	                    ->badge()
   248	                    ->formatStateUsing(fn(string $state) => ReceiptStatus::tryFrom($state)?->getLabel() ?? $state)
   249	                    ->color(fn(string $state) => ReceiptStatus::tryFrom($state)?->getColor() ?? 'gray'),
   250	
   251	                Tables\Columns\TextColumn::make('createdBy.name')
   252	                    ->label('بواسطة')
   253	                    ->toggleable(isToggledHiddenByDefault: true),
   254	
   255	                Tables\Columns\TextColumn::make('created_at')
   256	                    ->label('تاريخ الإنشاء')
   257	                    ->dateTime('Y-m-d H:i')
   258	                    ->sortable()
   259	                    ->toggleable(isToggledHiddenByDefault: true),
   260	            ])
   261	            ->defaultSort('created_at', 'desc')
   262	            ->filters([
   263	                Tables\Filters\SelectFilter::make('status')
   264	                    ->label('الحالة')
   265	                    ->options(ReceiptStatus::toFilamentOptions()),
   266	
   267	                Tables\Filters\SelectFilter::make('fee_category')
   268	                    ->label('التصنيف')
   269	                    ->options(FeeCategory::toFilamentOptions()),
   270	
   271	                Tables\Filters\SelectFilter::make('payment_method')
   272	                    ->label('طريقة الدفع')
   273	                    ->options([
   274	                        'cash' => 'نقدي',
   275	                        'bank_transfer' => 'تحويل بنكي',
   276	                        'check' => 'شيك',
   277	                        'credit_card' => 'بطاقة ائتمان',
   278	                        'pos' => 'نقطة بيع',
   279	                    ]),
   280	
   281	                Tables\Filters\Filter::make('receipt_date')
   282	                    ->form([
   283	                        Forms\Components\DatePicker::make('from')->label('من تاريخ'),
   284	                        Forms\Components\DatePicker::make('to')->label('إلى تاريخ'),
   285	                    ])
   286	                    ->query(function (Builder $query, array $data): Builder {
   287	                        return $query
   288	                            ->when($data['from'], fn(Builder $q, $date) => $q->whereDate('receipt_date', '>=', $date))
   289	                            ->when($data['to'], fn(Builder $q, $date) => $q->whereDate('receipt_date', '<=', $date));
   290	                    }),
   291	
   292	                Tables\Filters\Filter::make('today')
   293	                    ->label('إيصالات اليوم')
   294	                    ->query(fn(Builder $query) => $query->whereDate('receipt_date', now()->toDateString()))
   295	                    ->toggle(),
   296	            ])
   297	            ->actions([
   298	                Tables\Actions\ViewAction::make(),
   299	
   300	                Tables\Actions\Action::make('print')
   301	                    ->label('طباعة')
   302	                    ->icon('heroicon-o-printer')
   303	                    ->color('info')
   304	                    ->url(fn(Receipt $record) => route('receipts.print', $record), shouldOpenInNewTab: true),
   305	
   306	                Tables\Actions\Action::make('void')
   307	                    ->label('إلغاء')
   308	                    ->icon('heroicon-o-x-circle')
   309	                    ->color('danger')
   310	                    ->requiresConfirmation()
   311	                    ->modalHeading('إلغاء الإيصال')
   312	                    ->modalDescription('هل أنت متأكد من إلغاء هذا الإيصال؟ سيتم عكس جميع الحركات المالية المرتبطة.')
   313	                    ->form([
   314	                        Forms\Components\Textarea::make('void_reason')
   315	                            ->label('سبب الإلغاء')
   316	                            ->required(),
   317	                    ])
   318	                    ->action(function (Receipt $record, array $data) {
   319	                        app(\App\Services\Financial\ReceiptService::class)->voidReceipt($record, $data['void_reason']);
   320	                        \Filament\Notifications\Notification::make()
   321	                            ->title('تم إلغاء الإيصال بنجاح')
   322	                            ->success()
   323	                            ->send();
   324	                    })
   325	                    ->visible(fn(Receipt $record) => $record->canBeVoided()),
   326	            ])
   327	            ->bulkActions([
   328	                Tables\Actions\BulkAction::make('export')
   329	                    ->label('تصدير المحدد')
   330	                    ->icon('heroicon-o-arrow-down-tray')
   331	                    ->action(function (\Illuminate\Database\Eloquent\Collection $records) {
   332	                        // Export logic handled via maatwebsite/excel
   333	                        return response()->streamDownload(function () use ($records) {
   334	                            // Minimal CSV export
   335	                            $output = fopen('php://output', 'w');
   336	                            fprintf($output, chr(0xEF) . chr(0xBB) . chr(0xBF)); // BOM for Arabic
   337	                            fputcsv($output, ['رقم الإيصال', 'رقم العضوية', 'الاسم', 'المبلغ', 'المدفوع', 'الحالة', 'التاريخ']);
   338	                            foreach ($records as $receipt) {
   339	                                fputcsv($output, [
   340	                                    $receipt->receipt_number,
   341	                                    $receipt->member?->membership_number,
   342	                                    $receipt->member?->full_name_ar,
   343	                                    $receipt->total_amount,
   344	                                    $receipt->amount_paid,
   345	                                    $receipt->status,
   346	                                    $receipt->receipt_date?->format('Y-m-d'),
   347	                                ]);
   348	                            }
   349	                            fclose($output);
   350	                        }, 'receipts-' . now()->format('Y-m-d') . '.csv');
   351	                    }),
   352	            ]);
   353	    }
   354	
   355	    public static function infolist(Infolist $infolist): Infolist
   356	    {
   357	        return $infolist
   358	            ->schema([
   359	                Infolists\Components\Section::make('بيانات الإيصال')
   360	                    ->schema([
   361	                        Infolists\Components\TextEntry::make('receipt_number')
   362	                            ->label('رقم الإيصال')
   363	                            ->weight(FontWeight::Bold)
   364	                            ->size(Infolists\Components\TextEntry\TextEntrySize::Large)
   365	                            ->copyable(),
   366	
   367	                        Infolists\Components\TextEntry::make('status')
   368	                            ->label('الحالة')
   369	                            ->badge()
   370	                            ->formatStateUsing(fn(string $state) => ReceiptStatus::tryFrom($state)?->getLabel() ?? $state)
   371	                            ->color(fn(string $state) => ReceiptStatus::tryFrom($state)?->getColor() ?? 'gray'),
   372	
   373	                        Infolists\Components\TextEntry::make('receipt_date')
   374	                            ->label('التاريخ')
   375	                            ->date('Y-m-d'),
   376	
   377	                        Infolists\Components\TextEntry::make('fee_category')
   378	                            ->label('التصنيف')
   379	                            ->formatStateUsing(fn(string $state) => FeeCategory::tryFrom($state)?->getLabel() ?? $state),
   380	
   381	                        Infolists\Components\TextEntry::make('payment_method')
   382	                            ->label('طريقة الدفع')
   383	                            ->formatStateUsing(fn(string $state) => match ($state) {
   384	                                'cash' => 'نقدي',
   385	                                'bank_transfer' => 'تحويل بنكي',
   386	                                'check' => 'شيك',
   387	                                'credit_card' => 'بطاقة ائتمان',
   388	                                'pos' => 'نقطة بيع',
   389	                                default => $state,
   390	                            }),
   391	
   392	                        Infolists\Components\TextEntry::make('payment_reference')
   393	                            ->label('مرجع الدفع'),
   394	                    ])->columns(3),
   395	
   396	                Infolists\Components\Section::make('بيانات العضو')
   397	                    ->schema([
   398	                        Infolists\Components\TextEntry::make('member.membership_number')
   399	                            ->label('رقم العضوية'),
   400	                        Infolists\Components\TextEntry::make('member.full_name_ar')
   401	                            ->label('الاسم'),
   402	                        Infolists\Components\TextEntry::make('member.phone_primary')
   403	                            ->label('الهاتف'),
   404	                    ])->columns(3),
   405	
   406	                Infolists\Components\Section::make('المبالغ')
   407	                    ->schema([
   408	                        Infolists\Components\TextEntry::make('subtotal')
   409	                            ->label('الإجمالي الفرعي')
   410	                            ->money('EGP'),
   411	                        Infolists\Components\TextEntry::make('discount_amount')
   412	                            ->label('الخصم')
   413	                            ->money('EGP')
   414	                            ->color('warning'),
   415	                        Infolists\Components\TextEntry::make('discount_reason')
   416	                            ->label('سبب الخصم'),
   417	                        Infolists\Components\TextEntry::make('vat_amount')
   418	                            ->label('ضريبة القيمة المضافة')
   419	                            ->money('EGP'),
   420	                        Infolists\Components\TextEntry::make('total_amount')
   421	                            ->label('الإجمالي')
   422	                            ->money('EGP')
   423	                            ->weight(FontWeight::Bold)
   424	                            ->size(Infolists\Components\TextEntry\TextEntrySize::Large),
   425	                        Infolists\Components\TextEntry::make('amount_paid')
   426	                            ->label('المدفوع')
   427	                            ->money('EGP')
   428	                            ->color('success'),
   429	                        Infolists\Components\TextEntry::make('amount_remaining')
   430	                            ->label('المتبقي')
   431	                            ->money('EGP')
   432	                            ->color(fn(string $state) => (float) $state > 0 ? 'danger' : 'success'),
   433	                    ])->columns(4),
   434	
   435	                Infolists\Components\Section::make('معلومات الإلغاء')
   436	                    ->schema([
   437	                        Infolists\Components\TextEntry::make('void_reason')
   438	                            ->label('سبب الإلغاء'),
   439	                        Infolists\Components\TextEntry::make('voidedBy.name')
   440	                            ->label('ألغي بواسطة'),
   441	                        Infolists\Components\TextEntry::make('voided_at')
   442	                            ->label('تاريخ الإلغاء')
   443	                            ->dateTime('Y-m-d H:i'),
   444	                    ])->columns(3)
   445	                    ->visible(fn(Receipt $record) => $record->isVoided()),
   446	
   447	                Infolists\Components\Section::make('ملاحظات')
   448	                    ->schema([
   449	                        Infolists\Components\TextEntry::make('notes_ar')
   450	                            ->label('ملاحظات'),
   451	                        Infolists\Components\TextEntry::make('createdBy.name')
   452	                            ->label('أنشئ بواسطة'),
   453	                        Infolists\Components\TextEntry::make('created_at')
   454	                            ->label('تاريخ الإنشاء')
   455	                            ->dateTime('Y-m-d H:i'),
   456	                    ])->columns(3),
   457	            ]);
   458	    }
   459	
   460	    public static function getRelations(): array
   461	    {
   462	        return [
   463	            RelationManagers\ReceiptItemsRelationManager::class,
   464	        ];
   465	    }
   466	
   467	    public static function getPages(): array
   468	    {
   469	        return [
   470	            'index' => Pages\ListReceipts::route('/'),
   471	            'create' => Pages\CreateReceipt::route('/create'),
   472	            'view' => Pages\ViewReceipt::route('/{record}'),
   473	        ];
   474	    }
   475	
   476	    public static function getEloquentQuery(): Builder
   477	    {
   478	        return parent::getEloquentQuery()->where('is_archived', false);
   479	    }
   480	
   481	    public static function getNavigationBadge(): ?string
   482	    {
   483	        return static::getModel()::whereDate('receipt_date', now()->toDateString())
   484	            ->where('is_archived', false)
   485	            ->notVoided()
   486	            ->count() ?: null;
   487	    }
   488	
   489	    public static function getNavigationBadgeColor(): ?string
   490	    {
   491	        return 'success';
   492	    }
   493	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [163]: app/Filament/Resources/ReceiptResource.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [164/494]: app/Filament/Resources/ReceiptResource/Pages/CreateReceipt.php
│ LANGUAGE: php | LINES: 39 | SIZE: 1068 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Filament\Resources\ReceiptResource\Pages;
     4	
     5	use App\Filament\Resources\ReceiptResource;
     6	use App\Services\Financial\ReceiptService;
     7	use Filament\Resources\Pages\CreateRecord;
     8	use Filament\Notifications\Notification;
     9	
    10	class CreateReceipt extends CreateRecord
    11	{
    12	    protected static string $resource = ReceiptResource::class;
    13	
    14	    protected function handleRecordCreation(array $data): \Illuminate\Database\Eloquent\Model
    15	    {
    16	        $receiptService = app(ReceiptService::class);
    17	
    18	        try {
    19	            return $receiptService->createReceipt($data);
    20	        } catch (\RuntimeException $e) {
    21	            Notification::make()
    22	                ->title('خطأ')
    23	                ->body($e->getMessage())
    24	                ->danger()
    25	                ->send();
    26	
    27	            $this->halt();
    28	        }
    29	    }
    30	
    31	    protected function getRedirectUrl(): string
    32	    {
    33	        return $this->getResource()::getUrl('index');
    34	    }
    35	
    36	    protected function getCreatedNotificationTitle(): ?string
    37	    {
    38	        return 'تم إنشاء الإيصال بنجاح';
    39	    }
    40	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [164]: app/Filament/Resources/ReceiptResource/Pages/CreateReceipt.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [165/494]: app/Filament/Resources/ReceiptResource/Pages/ListReceipts.php
│ LANGUAGE: php | LINES: 27 | SIZE: 659 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Filament\Resources\ReceiptResource\Pages;
     4	
     5	use App\Filament\Resources\ReceiptResource;
     6	use Filament\Actions;
     7	use Filament\Resources\Pages\ListRecords;
     8	
     9	class ListReceipts extends ListRecords
    10	{
    11	    protected static string $resource = ReceiptResource::class;
    12	
    13	    protected function getHeaderActions(): array
    14	    {
    15	        return [
    16	            Actions\CreateAction::make()
    17	                ->label('إيصال جديد')
    18	                ->icon('heroicon-o-plus'),
    19	        ];
    20	    }
    21	
    22	    protected function getHeaderWidgets(): array
    23	    {
    24	        return [
    25	            \App\Filament\Widgets\DailyReceiptsSummaryWidget::class,
    26	        ];
    27	    }
    28	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [165]: app/Filament/Resources/ReceiptResource/Pages/ListReceipts.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [166/494]: app/Filament/Resources/ReceiptResource/Pages/ViewReceipt.php
│ LANGUAGE: php | LINES: 47 | SIZE: 1812 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Filament\Resources\ReceiptResource\Pages;
     4	
     5	use App\Filament\Resources\ReceiptResource;
     6	use Filament\Actions;
     7	use Filament\Resources\Pages\ViewRecord;
     8	
     9	class ViewReceipt extends ViewRecord
    10	{
    11	    protected static string $resource = ReceiptResource::class;
    12	
    13	    protected function getHeaderActions(): array
    14	    {
    15	        return [
    16	            Actions\Action::make('print')
    17	                ->label('طباعة')
    18	                ->icon('heroicon-o-printer')
    19	                ->color('info')
    20	                ->url(fn() => route('receipts.print', $this->record), shouldOpenInNewTab: true),
    21	
    22	            Actions\Action::make('void')
    23	                ->label('إلغاء الإيصال')
    24	                ->icon('heroicon-o-x-circle')
    25	                ->color('danger')
    26	                ->requiresConfirmation()
    27	                ->modalHeading('إلغاء الإيصال')
    28	                ->modalDescription('هل أنت متأكد؟ سيتم عكس جميع الحركات المالية.')
    29	                ->form([
    30	                    \Filament\Forms\Components\Textarea::make('void_reason')
    31	                        ->label('سبب الإلغاء')
    32	                        ->required(),
    33	                ])
    34	                ->action(function (array $data) {
    35	                    app(\App\Services\Financial\ReceiptService::class)
    36	                        ->voidReceipt($this->record, $data['void_reason']);
    37	
    38	                    \Filament\Notifications\Notification::make()
    39	                        ->title('تم إلغاء الإيصال')
    40	                        ->success()
    41	                        ->send();
    42	
    43	                    $this->refreshFormData(['status', 'void_reason', 'voided_at', 'voided_by']);
    44	                })
    45	                ->visible(fn() => $this->record->canBeVoided()),
    46	        ];
    47	    }
    48	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [166]: app/Filament/Resources/ReceiptResource/Pages/ViewReceipt.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [167/494]: app/Filament/Resources/ReceiptResource/RelationManagers/ReceiptItemsRelationManager.php
│ LANGUAGE: php | LINES: 54 | SIZE: 1767 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Filament\Resources\ReceiptResource\RelationManagers;
     4	
     5	use App\Enums\FeeCategory;
     6	use Filament\Forms;
     7	use Filament\Forms\Form;
     8	use Filament\Resources\RelationManagers\RelationManager;
     9	use Filament\Tables;
    10	use Filament\Tables\Table;
    11	
    12	class ReceiptItemsRelationManager extends RelationManager
    13	{
    14	    protected static string $relationship = 'items';
    15	    protected static ?string $title = 'بنود الإيصال';
    16	    protected static ?string $modelLabel = 'بند';
    17	    protected static ?string $pluralModelLabel = 'البنود';
    18	
    19	    public function table(Table $table): Table
    20	    {
    21	        return $table
    22	            ->columns([
    23	                Tables\Columns\TextColumn::make('sort_order')
    24	                    ->label('#')
    25	                    ->sortable(),
    26	
    27	                Tables\Columns\TextColumn::make('fee_category')
    28	                    ->label('التصنيف')
    29	                    ->formatStateUsing(fn(string $state) => FeeCategory::tryFrom($state)?->getLabel() ?? $state)
    30	                    ->badge(),
    31	
    32	                Tables\Columns\TextColumn::make('description_ar')
    33	                    ->label('الوصف')
    34	                    ->wrap(),
    35	
    36	                Tables\Columns\TextColumn::make('amount')
    37	                    ->label('المبلغ')
    38	                    ->money('EGP'),
    39	
    40	                Tables\Columns\TextColumn::make('quantity')
    41	                    ->label('العدد'),
    42	
    43	                Tables\Columns\TextColumn::make('subtotal')
    44	                    ->label('الإجمالي')
    45	                    ->money('EGP')
    46	                    ->weight(\Filament\Support\Enums\FontWeight::Bold),
    47	            ])
    48	            ->defaultSort('sort_order');
    49	    }
    50	
    51	    public function isReadOnly(): bool
    52	    {
    53	        return true;
    54	    }
    55	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [167]: app/Filament/Resources/ReceiptResource/RelationManagers/ReceiptItemsRelationManager.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [168/494]: app/Filament/Resources/SequenceResource.php
│ LANGUAGE: php | LINES: 173 | SIZE: 6683 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Filament\Resources;
     4	
     5	use App\Filament\Resources\SequenceResource\Pages;
     6	use App\Models\Sequence;
     7	use App\Services\Admin\SequenceService;
     8	use Filament\Forms;
     9	use Filament\Forms\Form;
    10	use Filament\Resources\Resource;
    11	use Filament\Tables;
    12	use Filament\Tables\Table;
    13	use Filament\Notifications\Notification;
    14	
    15	class SequenceResource extends Resource
    16	{
    17	    protected static ?string $model = Sequence::class;
    18	
    19	    protected static ?string $navigationIcon = 'heroicon-o-numbered-list';
    20	
    21	    protected static ?string $navigationGroup = 'إدارة النظام';
    22	
    23	    protected static ?string $navigationLabel = 'التسلسلات';
    24	
    25	    protected static ?string $modelLabel = 'تسلسل';
    26	
    27	    protected static ?string $pluralModelLabel = 'التسلسلات';
    28	
    29	    protected static ?int $navigationSort = 6;
    30	
    31	    public static function form(Form $form): Form
    32	    {
    33	        return $form->schema([
    34	            Forms\Components\Section::make('بيانات التسلسل')
    35	                ->schema([
    36	                    Forms\Components\TextInput::make('sequence_key')
    37	                        ->label('مفتاح التسلسل')
    38	                        ->disabled()
    39	                        ->dehydrated(false),
    40	
    41	                    Forms\Components\TextInput::make('description_ar')
    42	                        ->label('الوصف (عربي)')
    43	                        ->disabled()
    44	                        ->dehydrated(false),
    45	
    46	                    Forms\Components\TextInput::make('prefix')
    47	                        ->label('البادئة')
    48	                        ->required()
    49	                        ->maxLength(20),
    50	
    51	                    Forms\Components\TextInput::make('suffix')
    52	                        ->label('اللاحقة')
    53	                        ->maxLength(20),
    54	
    55	                    Forms\Components\TextInput::make('current_value')
    56	                        ->label('القيمة الحالية')
    57	                        ->numeric()
    58	                        ->required(),
    59	
    60	                    Forms\Components\TextInput::make('start_value')
    61	                        ->label('قيمة البداية')
    62	                        ->numeric()
    63	                        ->required(),
    64	
    65	                    Forms\Components\TextInput::make('increment_by')
    66	                        ->label('مقدار الزيادة')
    67	                        ->numeric()
    68	                        ->default(1)
    69	                        ->required()
    70	                        ->minValue(1),
    71	
    72	                    Forms\Components\TextInput::make('pad_length')
    73	                        ->label('طول الترقيم')
    74	                        ->numeric()
    75	                        ->default(6)
    76	                        ->required()
    77	                        ->minValue(1)
    78	                        ->maxValue(20),
    79	
    80	                    Forms\Components\Toggle::make('reset_on_fiscal_year')
    81	                        ->label('إعادة التعيين في السنة المالية')
    82	                        ->helperText('يتم إعادة التسلسل عند بداية كل سنة مالية جديدة'),
    83	                ])->columns(2),
    84	        ]);
    85	    }
    86	
    87	    public static function table(Table $table): Table
    88	    {
    89	        return $table
    90	            ->columns([
    91	                Tables\Columns\TextColumn::make('sequence_key')
    92	                    ->label('المفتاح')
    93	                    ->searchable()
    94	                    ->sortable()
    95	                    ->badge()
    96	                    ->color('gray'),
    97	
    98	                Tables\Columns\TextColumn::make('description_ar')
    99	                    ->label('الوصف')
   100	                    ->searchable(),
   101	
   102	                Tables\Columns\TextColumn::make('prefix')
   103	                    ->label('البادئة')
   104	                    ->badge()
   105	                    ->color('info'),
   106	
   107	                Tables\Columns\TextColumn::make('formatted_current')
   108	                    ->label('القيمة الحالية')
   109	                    ->badge()
   110	                    ->color('success'),
   111	
   112	                Tables\Columns\TextColumn::make('current_value')
   113	                    ->label('القيمة الرقمية')
   114	                    ->numeric()
   115	                    ->sortable(),
   116	
   117	                Tables\Columns\TextColumn::make('pad_length')
   118	                    ->label('طول الترقيم')
   119	                    ->alignCenter(),
   120	
   121	                Tables\Columns\IconColumn::make('reset_on_fiscal_year')
   122	                    ->label('إعادة سنوية')
   123	                    ->boolean(),
   124	
   125	                Tables\Columns\TextColumn::make('last_reset_at')
   126	                    ->label('آخر إعادة')
   127	                    ->dateTime('d/m/Y')
   128	                    ->placeholder('—')
   129	                    ->toggleable(),
   130	            ])
   131	            ->defaultSort('sequence_key')
   132	            ->filters([
   133	                Tables\Filters\TernaryFilter::make('reset_on_fiscal_year')
   134	                    ->label('إعادة سنوية')
   135	                    ->trueLabel('نعم')
   136	                    ->falseLabel('لا'),
   137	            ])
   138	            ->actions([
   139	                Tables\Actions\EditAction::make(),
   140	
   141	                Tables\Actions\Action::make('reset')
   142	                    ->label('إعادة تعيين')
   143	                    ->icon('heroicon-o-arrow-path')
   144	                    ->color('danger')
   145	                    ->requiresConfirmation()
   146	                    ->modalHeading('إعادة تعيين التسلسل')
   147	                    ->modalDescription('هل أنت متأكد؟ ستتم إعادة تعيين هذا التسلسل إلى القيمة الابتدائية.')
   148	                    ->action(function (Sequence $record) {
   149	                        app(SequenceService::class)->resetSequence($record->sequence_key);
   150	                        Notification::make()->title('تم إعادة التعيين بنجاح')->success()->send();
   151	                    }),
   152	
   153	                Tables\Actions\Action::make('peek')
   154	                    ->label('معاينة التالي')
   155	                    ->icon('heroicon-o-eye')
   156	                    ->color('info')
   157	                    ->action(function (Sequence $record) {
   158	                        $next = app(SequenceService::class)->peekNextNumber($record->sequence_key);
   159	                        Notification::make()
   160	                            ->title("الرقم التالي: {$next}")
   161	                            ->info()
   162	                            ->send();
   163	                    }),
   164	            ]);
   165	    }
   166	
   167	    public static function getPages(): array
   168	    {
   169	        return [
   170	            'index' => Pages\ListSequences::route('/'),
   171	            'edit' => Pages\EditSequence::route('/{record}/edit'),
   172	        ];
   173	    }
   174	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [168]: app/Filament/Resources/SequenceResource.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [169/494]: app/Filament/Resources/SequenceResource/Pages/EditSequence.php
│ LANGUAGE: php | LINES: 16 | SIZE: 395 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Filament\Resources\SequenceResource\Pages;
     4	
     5	use App\Filament\Resources\SequenceResource;
     6	use Filament\Actions;
     7	use Filament\Resources\Pages\EditRecord;
     8	
     9	class EditSequence extends EditRecord
    10	{
    11	    protected static string $resource = SequenceResource::class;
    12	
    13	    protected function getRedirectUrl(): string
    14	    {
    15	        return $this->getResource()::getUrl('index');
    16	    }
    17	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [169]: app/Filament/Resources/SequenceResource/Pages/EditSequence.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [170/494]: app/Filament/Resources/SequenceResource/Pages/ListSequences.php
│ LANGUAGE: php | LINES: 30 | SIZE: 1293 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Filament\Resources\SequenceResource\Pages;
     4	
     5	use App\Filament\Resources\SequenceResource;
     6	use App\Services\Admin\SequenceService;
     7	use Filament\Actions;
     8	use Filament\Resources\Pages\ListRecords;
     9	use Filament\Notifications\Notification;
    10	
    11	class ListSequences extends ListRecords
    12	{
    13	    protected static string $resource = SequenceResource::class;
    14	
    15	    protected function getHeaderActions(): array
    16	    {
    17	        return [
    18	            Actions\Action::make('resetAllFiscalYear')
    19	                ->label('إعادة تعيين الكل (سنة مالية)')
    20	                ->icon('heroicon-o-arrow-path')
    21	                ->color('danger')
    22	                ->requiresConfirmation()
    23	                ->modalHeading('إعادة تعيين جميع تسلسلات السنة المالية')
    24	                ->modalDescription('سيتم إعادة تعيين جميع التسلسلات المحددة بإعادة السنة المالية. هذا الإجراء لا يمكن التراجع عنه.')
    25	                ->action(function () {
    26	                    $count = app(SequenceService::class)->resetAllFiscalYearSequences();
    27	                    Notification::make()->title("تم إعادة تعيين {$count} تسلسل")->success()->send();
    28	                }),
    29	        ];
    30	    }
    31	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [170]: app/Filament/Resources/SequenceResource/Pages/ListSequences.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [171/494]: app/Filament/Resources/SubscriptionPeriodResource.php
│ LANGUAGE: php | LINES: 358 | SIZE: 16241 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Filament\Resources;
     4	
     5	use App\Filament\Resources\SubscriptionPeriodResource\Pages;
     6	use App\Filament\Resources\SubscriptionPeriodResource\RelationManagers;
     7	use App\Models\SubscriptionPeriod;
     8	use Filament\Forms;
     9	use Filament\Forms\Form;
    10	use Filament\Resources\Resource;
    11	use Filament\Tables;
    12	use Filament\Tables\Table;
    13	use Filament\Infolists;
    14	use Filament\Infolists\Infolist;
    15	use Filament\Notifications\Notification;
    16	use Illuminate\Database\Eloquent\Builder;
    17	
    18	class SubscriptionPeriodResource extends Resource
    19	{
    20	    protected static ?string $model = SubscriptionPeriod::class;
    21	
    22	    protected static ?string $navigationIcon = 'heroicon-o-calendar-days';
    23	    protected static ?string $navigationGroup = 'الاشتراكات';
    24	    protected static ?string $navigationLabel = 'فترات الاشتراك';
    25	    protected static ?string $modelLabel = 'فترة اشتراك';
    26	    protected static ?string $pluralModelLabel = 'فترات الاشتراك';
    27	    protected static ?int $navigationSort = 1;
    28	
    29	    public static function form(Form $form): Form
    30	    {
    31	        return $form->schema([
    32	            Forms\Components\Section::make('بيانات الفترة')
    33	                ->description('المعلومات الأساسية لفترة الاشتراك')
    34	                ->icon('heroicon-o-calendar')
    35	                ->columns(3)
    36	                ->schema([
    37	                    Forms\Components\TextInput::make('name_ar')
    38	                        ->label('الاسم بالعربية')
    39	                        ->required()
    40	                        ->maxLength(100)
    41	                        ->placeholder('اشتراك 2025'),
    42	
    43	                    Forms\Components\TextInput::make('name_en')
    44	                        ->label('الاسم بالإنجليزية')
    45	                        ->maxLength(100)
    46	                        ->placeholder('Subscription 2025'),
    47	
    48	                    Forms\Components\TextInput::make('year')
    49	                        ->label('السنة')
    50	                        ->required()
    51	                        ->numeric()
    52	                        ->minValue(2020)
    53	                        ->maxValue(2099)
    54	                        ->default(now()->year)
    55	                        ->unique(ignoreRecord: true),
    56	
    57	                    Forms\Components\DatePicker::make('start_date')
    58	                        ->label('تاريخ البداية')
    59	                        ->required()
    60	                        ->default(now()->startOfYear())
    61	                        ->native(false)
    62	                        ->displayFormat('d/m/Y'),
    63	
    64	                    Forms\Components\DatePicker::make('end_date')
    65	                        ->label('تاريخ النهاية')
    66	                        ->required()
    67	                        ->default(now()->endOfYear())
    68	                        ->native(false)
    69	                        ->displayFormat('d/m/Y')
    70	                        ->after('start_date'),
    71	
    72	                    Forms\Components\DatePicker::make('grace_deadline')
    73	                        ->label('آخر موعد للسداد بدون غرامة')
    74	                        ->native(false)
    75	                        ->displayFormat('d/m/Y')
    76	                        ->after('start_date')
    77	                        ->helperText('يُحسب تلقائياً من أيام المهلة إذا لم يُحدد'),
    78	                ]),
    79	
    80	            Forms\Components\Section::make('الرسوم')
    81	                ->description('رسوم الاشتراك لكل فئة')
    82	                ->icon('heroicon-o-banknotes')
    83	                ->columns(3)
    84	                ->schema([
    85	                    Forms\Components\TextInput::make('member_fee')
    86	                        ->label('رسوم العضو')
    87	                        ->required()
    88	                        ->numeric()
    89	                        ->prefix('ج.م')
    90	                        ->default(0)
    91	                        ->minValue(0),
    92	
    93	                    Forms\Components\TextInput::make('spouse_fee')
    94	                        ->label('رسوم الزوج/الزوجة')
    95	                        ->required()
    96	                        ->numeric()
    97	                        ->prefix('ج.م')
    98	                        ->default(0)
    99	                        ->minValue(0),
   100	
   101	                    Forms\Components\TextInput::make('child_fee')
   102	                        ->label('رسوم الابن/الابنة')
   103	                        ->required()
   104	                        ->numeric()
   105	                        ->prefix('ج.م')
   106	                        ->default(0)
   107	                        ->minValue(0),
   108	                ]),
   109	
   110	            Forms\Components\Section::make('الغرامات والمهل')
   111	                ->description('إعدادات التأخير والغرامات')
   112	                ->icon('heroicon-o-exclamation-triangle')
   113	                ->columns(3)
   114	                ->schema([
   115	                    Forms\Components\TextInput::make('grace_period_days')
   116	                        ->label('أيام المهلة')
   117	                        ->required()
   118	                        ->numeric()
   119	                        ->default(90)
   120	                        ->minValue(0)
   121	                        ->suffix('يوم')
   122	                        ->helperText('عدد الأيام بعد بداية الفترة قبل تطبيق الغرامة'),
   123	
   124	                    Forms\Components\TextInput::make('late_fee')
   125	                        ->label('غرامة ثابتة')
   126	                        ->numeric()
   127	                        ->prefix('ج.م')
   128	                        ->default(0)
   129	                        ->minValue(0)
   130	                        ->helperText('مبلغ ثابت يُضاف كغرامة'),
   131	
   132	                    Forms\Components\TextInput::make('late_fee_percentage')
   133	                        ->label('نسبة غرامة')
   134	                        ->numeric()
   135	                        ->suffix('%')
   136	                        ->default(0)
   137	                        ->minValue(0)
   138	                        ->maxValue(100)
   139	                        ->helperText('نسبة من إجمالي الاشتراك — تُستخدم إذا كانت الغرامة الثابتة = 0'),
   140	                ]),
   141	
   142	            Forms\Components\Section::make('الإعدادات')
   143	                ->icon('heroicon-o-cog-6-tooth')
   144	                ->columns(3)
   145	                ->schema([
   146	                    Forms\Components\Toggle::make('is_active')
   147	                        ->label('نشطة')
   148	                        ->helperText('فترة واحدة فقط يمكن أن تكون نشطة')
   149	                        ->default(false),
   150	
   151	                    Forms\Components\Toggle::make('is_locked')
   152	                        ->label('مقفلة')
   153	                        ->helperText('لا يمكن التعديل بعد القفل')
   154	                        ->default(false),
   155	
   156	                    Forms\Components\Toggle::make('auto_generate')
   157	                        ->label('إنشاء تلقائي')
   158	                        ->helperText('إنشاء اشتراكات تلقائياً لجميع الأعضاء النشطين')
   159	                        ->default(true),
   160	
   161	                    Forms\Components\Textarea::make('notes')
   162	                        ->label('ملاحظات')
   163	                        ->rows(3)
   164	                        ->columnSpanFull(),
   165	                ]),
   166	        ]);
   167	    }
   168	
   169	    public static function table(Table $table): Table
   170	    {
   171	        return $table
   172	            ->columns([
   173	                Tables\Columns\TextColumn::make('year')
   174	                    ->label('السنة')
   175	                    ->sortable()
   176	                    ->searchable()
   177	                    ->weight('bold')
   178	                    ->size('lg'),
   179	
   180	                Tables\Columns\TextColumn::make('name_ar')
   181	                    ->label('الاسم')
   182	                    ->searchable()
   183	                    ->limit(30),
   184	
   185	                Tables\Columns\TextColumn::make('start_date')
   186	                    ->label('البداية')
   187	                    ->date('d/m/Y')
   188	                    ->sortable(),
   189	
   190	                Tables\Columns\TextColumn::make('end_date')
   191	                    ->label('النهاية')
   192	                    ->date('d/m/Y')
   193	                    ->sortable(),
   194	
   195	                Tables\Columns\TextColumn::make('member_fee')
   196	                    ->label('رسوم العضو')
   197	                    ->money('EGP')
   198	                    ->sortable(),
   199	
   200	                Tables\Columns\TextColumn::make('spouse_fee')
   201	                    ->label('رسوم الزوج/ة')
   202	                    ->money('EGP'),
   203	
   204	                Tables\Columns\TextColumn::make('child_fee')
   205	                    ->label('رسوم الابن/ة')
   206	                    ->money('EGP'),
   207	
   208	                Tables\Columns\TextColumn::make('subscriptions_count')
   209	                    ->label('عدد الاشتراكات')
   210	                    ->counts('subscriptions')
   211	                    ->sortable()
   212	                    ->badge()
   213	                    ->color('info'),
   214	
   215	                Tables\Columns\IconColumn::make('is_active')
   216	                    ->label('نشطة')
   217	                    ->boolean()
   218	                    ->sortable(),
   219	
   220	                Tables\Columns\IconColumn::make('is_locked')
   221	                    ->label('مقفلة')
   222	                    ->boolean()
   223	                    ->trueIcon('heroicon-o-lock-closed')
   224	                    ->falseIcon('heroicon-o-lock-open')
   225	                    ->trueColor('danger')
   226	                    ->falseColor('success'),
   227	            ])
   228	            ->defaultSort('year', 'desc')
   229	            ->filters([
   230	                Tables\Filters\TernaryFilter::make('is_active')
   231	                    ->label('نشطة'),
   232	
   233	                Tables\Filters\TernaryFilter::make('is_locked')
   234	                    ->label('مقفلة'),
   235	            ])
   236	            ->actions([
   237	                Tables\Actions\ViewAction::make(),
   238	                Tables\Actions\EditAction::make(),
   239	
   240	                Tables\Actions\Action::make('bulk_generate')
   241	                    ->label('إنشاء اشتراكات')
   242	                    ->icon('heroicon-o-bolt')
   243	                    ->color('success')
   244	                    ->requiresConfirmation()
   245	                    ->modalHeading('إنشاء اشتراكات لجميع الأعضاء النشطين')
   246	                    ->modalDescription('سيتم إنشاء اشتراك لكل عضو نشط ليس لديه اشتراك في هذه الفترة. هل أنت متأكد؟')
   247	                    ->action(function (SubscriptionPeriod $record) {
   248	                        $service = app(\App\Services\Subscription\RenewalService::class);
   249	                        $results = $service->bulkGenerate($record);
   250	
   251	                        Notification::make()
   252	                            ->title('تم إنشاء الاشتراكات')
   253	                            ->body("تم إنشاء: {$results['created']} — تم تخطي: {$results['skipped']} — أخطاء: " . count($results['errors']))
   254	                            ->success()
   255	                            ->send();
   256	                    })
   257	                    ->visible(fn (SubscriptionPeriod $record) => !$record->is_locked),
   258	
   259	                Tables\Actions\Action::make('apply_late_fees')
   260	                    ->label('تطبيق غرامات التأخير')
   261	                    ->icon('heroicon-o-exclamation-triangle')
   262	                    ->color('danger')
   263	                    ->requiresConfirmation()
   264	                    ->modalHeading('تطبيق غرامات التأخير')
   265	                    ->modalDescription('سيتم تطبيق غرامات التأخير على جميع الاشتراكات المتأخرة في هذه الفترة. هذا الإجراء لا يمكن التراجع عنه.')
   266	                    ->action(function (SubscriptionPeriod $record) {
   267	                        $service = app(\App\Services\Subscription\RenewalService::class);
   268	                        $results = $service->applyLateFees($record);
   269	
   270	                        Notification::make()
   271	                            ->title('تم تطبيق الغرامات')
   272	                            ->body("تم تطبيق: {$results['applied']} — تم تخطي: {$results['skipped']} — أخطاء: " . count($results['errors']))
   273	                            ->warning()
   274	                            ->send();
   275	                    })
   276	                    ->visible(fn (SubscriptionPeriod $record) => $record->is_overdue),
   277	            ])
   278	            ->bulkActions([
   279	                Tables\Actions\BulkActionGroup::make([
   280	                    Tables\Actions\DeleteBulkAction::make(),
   281	                ]),
   282	            ]);
   283	    }
   284	
   285	    public static function infolist(Infolist $infolist): Infolist
   286	    {
   287	        return $infolist->schema([
   288	            Infolists\Components\Section::make('بيانات الفترة')
   289	                ->columns(3)
   290	                ->schema([
   291	                    Infolists\Components\TextEntry::make('name_ar')->label('الاسم بالعربية'),
   292	                    Infolists\Components\TextEntry::make('name_en')->label('الاسم بالإنجليزية'),
   293	                    Infolists\Components\TextEntry::make('year')->label('السنة')->badge(),
   294	                    Infolists\Components\TextEntry::make('start_date')->label('البداية')->date('d/m/Y'),
   295	                    Infolists\Components\TextEntry::make('end_date')->label('النهاية')->date('d/m/Y'),
   296	                    Infolists\Components\TextEntry::make('grace_deadline')->label('آخر موعد بدون غرامة')->date('d/m/Y'),
   297	                ]),
   298	
   299	            Infolists\Components\Section::make('الرسوم')
   300	                ->columns(3)
   301	                ->schema([
   302	                    Infolists\Components\TextEntry::make('member_fee')->label('رسوم العضو')->money('EGP'),
   303	                    Infolists\Components\TextEntry::make('spouse_fee')->label('رسوم الزوج/ة')->money('EGP'),
   304	                    Infolists\Components\TextEntry::make('child_fee')->label('رسوم الابن/ة')->money('EGP'),
   305	                    Infolists\Components\TextEntry::make('late_fee')->label('غرامة ثابتة')->money('EGP'),
   306	                    Infolists\Components\TextEntry::make('late_fee_percentage')->label('نسبة غرامة')->suffix('%'),
   307	                    Infolists\Components\TextEntry::make('grace_period_days')->label('أيام المهلة')->suffix(' يوم'),
   308	                ]),
   309	
   310	            Infolists\Components\Section::make('الإحصائيات')
   311	                ->columns(4)
   312	                ->schema([
   313	                    Infolists\Components\TextEntry::make('total_subscriptions_count')
   314	                        ->label('إجمالي الاشتراكات')
   315	                        ->badge()
   316	                        ->color('info'),
   317	                    Infolists\Components\TextEntry::make('paid_count')
   318	                        ->label('المدفوع')
   319	                        ->badge()
   320	                        ->color('success'),
   321	                    Infolists\Components\TextEntry::make('collection_rate')
   322	                        ->label('نسبة التحصيل')
   323	                        ->suffix('%')
   324	                        ->badge()
   325	                        ->color(fn ($state) => $state >= 80 ? 'success' : ($state >= 50 ? 'warning' : 'danger')),
   326	                    Infolists\Components\IconEntry::make('is_active')
   327	                        ->label('نشطة')
   328	                        ->boolean(),
   329	                ]),
   330	        ]);
   331	    }
   332	
   333	    public static function getRelations(): array
   334	    {
   335	        return [
   336	            RelationManagers\SubscriptionsRelationManager::class,
   337	        ];
   338	    }
   339	
   340	    public static function getPages(): array
   341	    {
   342	        return [
   343	            'index'  => Pages\ListSubscriptionPeriods::route('/'),
   344	            'create' => Pages\CreateSubscriptionPeriod::route('/create'),
   345	            'view'   => Pages\ViewSubscriptionPeriod::route('/{record}'),
   346	            'edit'   => Pages\EditSubscriptionPeriod::route('/{record}/edit'),
   347	        ];
   348	    }
   349	
   350	    public static function getNavigationBadge(): ?string
   351	    {
   352	        return static::getModel()::active()->count() ?: null;
   353	    }
   354	
   355	    public static function getNavigationBadgeColor(): ?string
   356	    {
   357	        return 'primary';
   358	    }
   359	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [171]: app/Filament/Resources/SubscriptionPeriodResource.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [172/494]: app/Filament/Resources/SubscriptionPeriodResource/Pages/CreateSubscriptionPeriod.php
│ LANGUAGE: php | LINES: 20 | SIZE: 565 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Filament\Resources\SubscriptionPeriodResource\Pages;
     4	
     5	use App\Filament\Resources\SubscriptionPeriodResource;
     6	use Filament\Resources\Pages\CreateRecord;
     7	
     8	class CreateSubscriptionPeriod extends CreateRecord
     9	{
    10	    protected static string $resource = SubscriptionPeriodResource::class;
    11	
    12	    protected function getRedirectUrl(): string
    13	    {
    14	        return $this->getResource()::getUrl('index');
    15	    }
    16	
    17	    protected function getCreatedNotificationTitle(): ?string
    18	    {
    19	        return 'تم إنشاء فترة الاشتراك بنجاح';
    20	    }
    21	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [172]: app/Filament/Resources/SubscriptionPeriodResource/Pages/CreateSubscriptionPeriod.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [173/494]: app/Filament/Resources/SubscriptionPeriodResource/Pages/EditSubscriptionPeriod.php
│ LANGUAGE: php | LINES: 29 | SIZE: 784 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Filament\Resources\SubscriptionPeriodResource\Pages;
     4	
     5	use App\Filament\Resources\SubscriptionPeriodResource;
     6	use Filament\Actions;
     7	use Filament\Resources\Pages\EditRecord;
     8	
     9	class EditSubscriptionPeriod extends EditRecord
    10	{
    11	    protected static string $resource = SubscriptionPeriodResource::class;
    12	
    13	    protected function getHeaderActions(): array
    14	    {
    15	        return [
    16	            Actions\ViewAction::make(),
    17	            Actions\DeleteAction::make(),
    18	        ];
    19	    }
    20	
    21	    protected function getRedirectUrl(): string
    22	    {
    23	        return $this->getResource()::getUrl('view', ['record' => $this->getRecord()]);
    24	    }
    25	
    26	    protected function getSavedNotificationTitle(): ?string
    27	    {
    28	        return 'تم تحديث فترة الاشتراك بنجاح';
    29	    }
    30	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [173]: app/Filament/Resources/SubscriptionPeriodResource/Pages/EditSubscriptionPeriod.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [174/494]: app/Filament/Resources/SubscriptionPeriodResource/Pages/ListSubscriptionPeriods.php
│ LANGUAGE: php | LINES: 20 | SIZE: 557 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Filament\Resources\SubscriptionPeriodResource\Pages;
     4	
     5	use App\Filament\Resources\SubscriptionPeriodResource;
     6	use Filament\Actions;
     7	use Filament\Resources\Pages\ListRecords;
     8	
     9	class ListSubscriptionPeriods extends ListRecords
    10	{
    11	    protected static string $resource = SubscriptionPeriodResource::class;
    12	
    13	    protected function getHeaderActions(): array
    14	    {
    15	        return [
    16	            Actions\CreateAction::make()
    17	                ->label('فترة اشتراك جديدة')
    18	                ->icon('heroicon-o-plus'),
    19	        ];
    20	    }
    21	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [174]: app/Filament/Resources/SubscriptionPeriodResource/Pages/ListSubscriptionPeriods.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [175/494]: app/Filament/Resources/SubscriptionPeriodResource/Pages/ViewSubscriptionPeriod.php
│ LANGUAGE: php | LINES: 18 | SIZE: 450 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Filament\Resources\SubscriptionPeriodResource\Pages;
     4	
     5	use App\Filament\Resources\SubscriptionPeriodResource;
     6	use Filament\Actions;
     7	use Filament\Resources\Pages\ViewRecord;
     8	
     9	class ViewSubscriptionPeriod extends ViewRecord
    10	{
    11	    protected static string $resource = SubscriptionPeriodResource::class;
    12	
    13	    protected function getHeaderActions(): array
    14	    {
    15	        return [
    16	            Actions\EditAction::make(),
    17	        ];
    18	    }
    19	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [175]: app/Filament/Resources/SubscriptionPeriodResource/Pages/ViewSubscriptionPeriod.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [176/494]: app/Filament/Resources/SubscriptionPeriodResource/RelationManagers/SubscriptionsRelationManager.php
│ LANGUAGE: php | LINES: 87 | SIZE: 3283 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Filament\Resources\SubscriptionPeriodResource\RelationManagers;
     4	
     5	use App\Enums\SubscriptionStatus;
     6	use Filament\Forms;
     7	use Filament\Forms\Form;
     8	use Filament\Resources\RelationManagers\RelationManager;
     9	use Filament\Tables;
    10	use Filament\Tables\Table;
    11	
    12	class SubscriptionsRelationManager extends RelationManager
    13	{
    14	    protected static string $relationship = 'subscriptions';
    15	    protected static ?string $title = 'الاشتراكات';
    16	    protected static ?string $modelLabel = 'اشتراك';
    17	
    18	    public function table(Table $table): Table
    19	    {
    20	        return $table
    21	            ->columns([
    22	                Tables\Columns\TextColumn::make('member.name_ar')
    23	                    ->label('العضو')
    24	                    ->searchable()
    25	                    ->sortable()
    26	                    ->limit(30),
    27	
    28	                Tables\Columns\TextColumn::make('member.membership_number')
    29	                    ->label('رقم العضوية')
    30	                    ->searchable()
    31	                    ->sortable()
    32	                    ->badge()
    33	                    ->color('info'),
    34	
    35	                Tables\Columns\TextColumn::make('net_amount')
    36	                    ->label('المبلغ المطلوب')
    37	                    ->money('EGP')
    38	                    ->sortable(),
    39	
    40	                Tables\Columns\TextColumn::make('paid_amount')
    41	                    ->label('المدفوع')
    42	                    ->money('EGP')
    43	                    ->sortable()
    44	                    ->color('success'),
    45	
    46	                Tables\Columns\TextColumn::make('balance')
    47	                    ->label('المتبقي')
    48	                    ->money('EGP')
    49	                    ->sortable()
    50	                    ->color(fn ($state) => $state > 0 ? 'danger' : 'success'),
    51	
    52	                Tables\Columns\TextColumn::make('status')
    53	                    ->label('الحالة')
    54	                    ->badge()
    55	                    ->formatStateUsing(fn (SubscriptionStatus $state) => $state->label())
    56	                    ->color(fn (SubscriptionStatus $state) => $state->color())
    57	                    ->icon(fn (SubscriptionStatus $state) => $state->icon()),
    58	
    59	                Tables\Columns\IconColumn::make('is_late')
    60	                    ->label('متأخر')
    61	                    ->boolean()
    62	                    ->trueColor('danger'),
    63	
    64	                Tables\Columns\TextColumn::make('paid_date')
    65	                    ->label('تاريخ الدفع')
    66	                    ->date('d/m/Y')
    67	                    ->sortable()
    68	                    ->placeholder('—'),
    69	            ])
    70	            ->defaultSort('member.membership_number')
    71	            ->filters([
    72	                Tables\Filters\SelectFilter::make('status')
    73	                    ->label('الحالة')
    74	                    ->options(collect(SubscriptionStatus::cases())->mapWithKeys(
    75	                        fn ($s) => [$s->value => $s->label()]
    76	                    )),
    77	
    78	                Tables\Filters\TernaryFilter::make('is_late')
    79	                    ->label('متأخر'),
    80	            ])
    81	            ->headerActions([])
    82	            ->actions([
    83	                Tables\Actions\ViewAction::make()
    84	                    ->url(fn ($record) => route('filament.admin.resources.subscriptions.view', $record)),
    85	            ])
    86	            ->bulkActions([]);
    87	    }
    88	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [176]: app/Filament/Resources/SubscriptionPeriodResource/RelationManagers/SubscriptionsRelationManager.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [177/494]: app/Filament/Resources/SubscriptionResource.php
│ LANGUAGE: php | LINES: 552 | SIZE: 25175 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Filament\Resources;
     4	
     5	use App\Enums\PaymentMethod;
     6	use App\Enums\SubscriptionStatus;
     7	use App\Filament\Resources\SubscriptionResource\Pages;
     8	use App\Models\Member;
     9	use App\Models\Subscription;
    10	use App\Models\SubscriptionPeriod;
    11	use App\Services\Subscription\SubscriptionCalculatorService;
    12	use App\Services\Subscription\SubscriptionService;
    13	use Filament\Forms;
    14	use Filament\Forms\Form;
    15	use Filament\Forms\Get;
    16	use Filament\Forms\Set;
    17	use Filament\Infolists;
    18	use Filament\Infolists\Infolist;
    19	use Filament\Notifications\Notification;
    20	use Filament\Resources\Resource;
    21	use Filament\Tables;
    22	use Filament\Tables\Table;
    23	use Illuminate\Database\Eloquent\Builder;
    24	
    25	class SubscriptionResource extends Resource
    26	{
    27	    protected static ?string $model = Subscription::class;
    28	
    29	    protected static ?string $navigationIcon = 'heroicon-o-currency-dollar';
    30	    protected static ?string $navigationGroup = 'الاشتراكات';
    31	    protected static ?string $navigationLabel = 'الاشتراكات';
    32	    protected static ?string $modelLabel = 'اشتراك';
    33	    protected static ?string $pluralModelLabel = 'الاشتراكات';
    34	    protected static ?int $navigationSort = 2;
    35	
    36	    public static function form(Form $form): Form
    37	    {
    38	        return $form->schema([
    39	            Forms\Components\Section::make('بيانات الاشتراك')
    40	                ->description('اختر العضو وفترة الاشتراك')
    41	                ->icon('heroicon-o-document-text')
    42	                ->columns(2)
    43	                ->schema([
    44	                    Forms\Components\Select::make('member_id')
    45	                        ->label('العضو')
    46	                        ->relationship('member', 'name_ar', fn (Builder $query) => $query->where('status', 'active'))
    47	                        ->getOptionLabelFromRecordUsing(fn ($record) => "{$record->membership_number} — {$record->name_ar}")
    48	                        ->searchable(['name_ar', 'name_en', 'membership_number', 'national_id'])
    49	                        ->preload()
    50	                        ->required()
    51	                        ->live()
    52	                        ->afterStateUpdated(function (Get $get, Set $set) {
    53	                            static::recalculateFees($get, $set);
    54	                        }),
    55	
    56	                    Forms\Components\Select::make('subscription_period_id')
    57	                        ->label('فترة الاشتراك')
    58	                        ->relationship('subscriptionPeriod', 'name_ar')
    59	                        ->getOptionLabelFromRecordUsing(fn ($record) => "{$record->year} — {$record->name_ar}")
    60	                        ->required()
    61	                        ->preload()
    62	                        ->live()
    63	                        ->afterStateUpdated(function (Get $get, Set $set) {
    64	                            static::recalculateFees($get, $set);
    65	                        }),
    66	                ]),
    67	
    68	            Forms\Components\Section::make('حساب الرسوم')
    69	                ->description('يتم حساب الرسوم تلقائياً بناءً على العضو والفترة')
    70	                ->icon('heroicon-o-calculator')
    71	                ->columns(3)
    72	                ->schema([
    73	                    Forms\Components\TextInput::make('base_amount')
    74	                        ->label('رسوم العضو')
    75	                        ->numeric()
    76	                        ->prefix('ج.م')
    77	                        ->disabled()
    78	                        ->dehydrated(),
    79	
    80	                    Forms\Components\TextInput::make('dependents_amount')
    81	                        ->label('رسوم التابعين')
    82	                        ->numeric()
    83	                        ->prefix('ج.م')
    84	                        ->disabled()
    85	                        ->dehydrated(),
    86	
    87	                    Forms\Components\TextInput::make('total_amount')
    88	                        ->label('الإجمالي')
    89	                        ->numeric()
    90	                        ->prefix('ج.م')
    91	                        ->disabled()
    92	                        ->dehydrated(),
    93	
    94	                    Forms\Components\TextInput::make('discount_amount')
    95	                        ->label('الخصم')
    96	                        ->numeric()
    97	                        ->prefix('ج.م')
    98	                        ->default(0)
    99	                        ->minValue(0)
   100	                        ->live(debounce: 500)
   101	                        ->afterStateUpdated(function (Get $get, Set $set) {
   102	                            static::recalculateFees($get, $set);
   103	                        }),
   104	
   105	                    Forms\Components\TextInput::make('discount_reason')
   106	                        ->label('سبب الخصم')
   107	                        ->visible(fn (Get $get) => (float) ($get('discount_amount') ?? 0) > 0),
   108	
   109	                    Forms\Components\TextInput::make('late_fee_amount')
   110	                        ->label('غرامة التأخير')
   111	                        ->numeric()
   112	                        ->prefix('ج.م')
   113	                        ->disabled()
   114	                        ->dehydrated(),
   115	
   116	                    Forms\Components\TextInput::make('net_amount')
   117	                        ->label('الصافي المطلوب')
   118	                        ->numeric()
   119	                        ->prefix('ج.م')
   120	                        ->disabled()
   121	                        ->dehydrated()
   122	                        ->extraAttributes(['class' => 'font-bold text-lg']),
   123	                ]),
   124	
   125	            Forms\Components\Section::make('التواريخ والملاحظات')
   126	                ->icon('heroicon-o-calendar')
   127	                ->columns(2)
   128	                ->schema([
   129	                    Forms\Components\DatePicker::make('due_date')
   130	                        ->label('تاريخ الاستحقاق')
   131	                        ->native(false)
   132	                        ->displayFormat('d/m/Y'),
   133	
   134	                    Forms\Components\Textarea::make('notes')
   135	                        ->label('ملاحظات')
   136	                        ->rows(3)
   137	                        ->columnSpanFull(),
   138	                ]),
   139	        ]);
   140	    }
   141	
   142	    protected static function recalculateFees(Get $get, Set $set): void
   143	    {
   144	        $memberId = $get('member_id');
   145	        $periodId = $get('subscription_period_id');
   146	
   147	        if (!$memberId || !$periodId) return;
   148	
   149	        $member = Member::find($memberId);
   150	        $period = SubscriptionPeriod::find($periodId);
   151	
   152	        if (!$member || !$period) return;
   153	
   154	        $calculator = app(SubscriptionCalculatorService::class);
   155	        $discount = (float) ($get('discount_amount') ?? 0);
   156	        $calculation = $calculator->calculate($member, $period, $discount);
   157	
   158	        $set('base_amount', $calculation['base_amount']);
   159	        $set('dependents_amount', $calculation['dependents_amount']);
   160	        $set('total_amount', $calculation['total_amount']);
   161	        $set('late_fee_amount', $calculation['late_fee_amount']);
   162	        $set('net_amount', $calculation['net_amount']);
   163	    }
   164	
   165	    public static function table(Table $table): Table
   166	    {
   167	        return $table
   168	            ->columns([
   169	                Tables\Columns\TextColumn::make('member.membership_number')
   170	                    ->label('رقم العضوية')
   171	                    ->searchable()
   172	                    ->sortable()
   173	                    ->badge()
   174	                    ->color('info'),
   175	
   176	                Tables\Columns\TextColumn::make('member.name_ar')
   177	                    ->label('اسم العضو')
   178	                    ->searchable()
   179	                    ->sortable()
   180	                    ->limit(25),
   181	
   182	                Tables\Columns\TextColumn::make('subscriptionPeriod.year')
   183	                    ->label('السنة')
   184	                    ->sortable()
   185	                    ->badge()
   186	                    ->color('gray'),
   187	
   188	                Tables\Columns\TextColumn::make('net_amount')
   189	                    ->label('المطلوب')
   190	                    ->money('EGP')
   191	                    ->sortable(),
   192	
   193	                Tables\Columns\TextColumn::make('paid_amount')
   194	                    ->label('المدفوع')
   195	                    ->money('EGP')
   196	                    ->sortable()
   197	                    ->color('success'),
   198	
   199	                Tables\Columns\TextColumn::make('balance')
   200	                    ->label('المتبقي')
   201	                    ->money('EGP')
   202	                    ->sortable()
   203	                    ->color(fn ($state) => $state > 0 ? 'danger' : 'success')
   204	                    ->weight(fn ($state) => $state > 0 ? 'bold' : 'normal'),
   205	
   206	                Tables\Columns\TextColumn::make('discount_amount')
   207	                    ->label('الخصم')
   208	                    ->money('EGP')
   209	                    ->sortable()
   210	                    ->toggleable(isToggledHiddenByDefault: true),
   211	
   212	                Tables\Columns\TextColumn::make('late_fee_amount')
   213	                    ->label('غرامة')
   214	                    ->money('EGP')
   215	                    ->sortable()
   216	                    ->toggleable(isToggledHiddenByDefault: true),
   217	
   218	                Tables\Columns\TextColumn::make('status')
   219	                    ->label('الحالة')
   220	                    ->badge()
   221	                    ->formatStateUsing(fn (SubscriptionStatus $state) => $state->label())
   222	                    ->color(fn (SubscriptionStatus $state) => $state->color())
   223	                    ->icon(fn (SubscriptionStatus $state) => $state->icon())
   224	                    ->sortable(),
   225	
   226	                Tables\Columns\TextColumn::make('due_date')
   227	                    ->label('الاستحقاق')
   228	                    ->date('d/m/Y')
   229	                    ->sortable()
   230	                    ->toggleable(),
   231	
   232	                Tables\Columns\TextColumn::make('paid_date')
   233	                    ->label('تاريخ الدفع')
   234	                    ->date('d/m/Y')
   235	                    ->sortable()
   236	                    ->placeholder('—')
   237	                    ->toggleable(),
   238	
   239	                Tables\Columns\IconColumn::make('is_late')
   240	                    ->label('متأخر')
   241	                    ->boolean()
   242	                    ->trueColor('danger')
   243	                    ->toggleable(isToggledHiddenByDefault: true),
   244	            ])
   245	            ->defaultSort('created_at', 'desc')
   246	            ->filters([
   247	                Tables\Filters\SelectFilter::make('status')
   248	                    ->label('الحالة')
   249	                    ->multiple()
   250	                    ->options(collect(SubscriptionStatus::cases())->mapWithKeys(
   251	                        fn ($s) => [$s->value => $s->label()]
   252	                    )),
   253	
   254	                Tables\Filters\SelectFilter::make('subscription_period_id')
   255	                    ->label('الفترة')
   256	                    ->relationship('subscriptionPeriod', 'name_ar')
   257	                    ->preload()
   258	                    ->searchable(),
   259	
   260	                Tables\Filters\TernaryFilter::make('is_late')
   261	                    ->label('متأخر'),
   262	
   263	                Tables\Filters\TernaryFilter::make('is_waived')
   264	                    ->label('معفى'),
   265	
   266	                Tables\Filters\Filter::make('has_balance')
   267	                    ->label('عليه رصيد')
   268	                    ->query(fn (Builder $query) => $query->where('balance', '>', 0)),
   269	
   270	                Tables\Filters\Filter::make('overdue')
   271	                    ->label('متأخر السداد')
   272	                    ->query(fn (Builder $query) => $query->where('status', SubscriptionStatus::OVERDUE)),
   273	            ])
   274	            ->filtersFormColumns(3)
   275	            ->actions([
   276	                Tables\Actions\ActionGroup::make([
   277	                    Tables\Actions\ViewAction::make(),
   278	                    Tables\Actions\EditAction::make()
   279	                        ->visible(fn (Subscription $record) => !$record->status->isTerminal()),
   280	
   281	                    Tables\Actions\Action::make('record_payment')
   282	                        ->label('تسجيل دفع')
   283	                        ->icon('heroicon-o-banknotes')
   284	                        ->color('success')
   285	                        ->visible(fn (Subscription $record) => $record->status->isPayable())
   286	                        ->form([
   287	                            Forms\Components\TextInput::make('amount')
   288	                                ->label('المبلغ')
   289	                                ->numeric()
   290	                                ->required()
   291	                                ->prefix('ج.م')
   292	                                ->default(fn (Subscription $record) => $record->remaining_balance)
   293	                                ->minValue(0.01),
   294	
   295	                            Forms\Components\Select::make('payment_method')
   296	                                ->label('طريقة الدفع')
   297	                                ->options(collect(PaymentMethod::cases())->mapWithKeys(
   298	                                    fn ($m) => [$m->value => $m->label()]
   299	                                ))
   300	                                ->default('cash')
   301	                                ->required(),
   302	
   303	                            Forms\Components\TextInput::make('reference_number')
   304	                                ->label('رقم المرجع')
   305	                                ->placeholder('رقم الشيك أو التحويل'),
   306	
   307	                            Forms\Components\Textarea::make('notes')
   308	                                ->label('ملاحظات')
   309	                                ->rows(2),
   310	                        ])
   311	                        ->action(function (Subscription $record, array $data) {
   312	                            try {
   313	                                $service = app(SubscriptionService::class);
   314	                                $service->recordPayment(
   315	                                    subscription: $record,
   316	                                    amount: (float) $data['amount'],
   317	                                    paymentMethod: $data['payment_method'],
   318	                                    referenceNumber: $data['reference_number'] ?? null,
   319	                                    notes: $data['notes'] ?? null,
   320	                                );
   321	
   322	                                Notification::make()
   323	                                    ->title('تم تسجيل الدفع بنجاح')
   324	                                    ->body("تم دفع {$data['amount']} ج.م للاشتراك")
   325	                                    ->success()
   326	                                    ->send();
   327	                            } catch (\DomainException $e) {
   328	                                Notification::make()
   329	                                    ->title('خطأ')
   330	                                    ->body($e->getMessage())
   331	                                    ->danger()
   332	                                    ->send();
   333	                            }
   334	                        }),
   335	
   336	                    Tables\Actions\Action::make('recalculate')
   337	                        ->label('إعادة حساب')
   338	                        ->icon('heroicon-o-calculator')
   339	                        ->color('warning')
   340	                        ->visible(fn (Subscription $record) => !$record->status->isTerminal())
   341	                        ->requiresConfirmation()
   342	                        ->action(function (Subscription $record) {
   343	                            try {
   344	                                $service = app(SubscriptionService::class);
   345	                                $service->recalculate($record);
   346	
   347	                                Notification::make()
   348	                                    ->title('تم إعادة الحساب')
   349	                                    ->success()
   350	                                    ->send();
   351	                            } catch (\DomainException $e) {
   352	                                Notification::make()
   353	                                    ->title('خطأ')
   354	                                    ->body($e->getMessage())
   355	                                    ->danger()
   356	                                    ->send();
   357	                            }
   358	                        }),
   359	
   360	                    Tables\Actions\Action::make('waive')
   361	                        ->label('إعفاء')
   362	                        ->icon('heroicon-o-shield-check')
   363	                        ->color('gray')
   364	                        ->visible(fn (Subscription $record) => !$record->status->isTerminal())
   365	                        ->form([
   366	                            Forms\Components\Textarea::make('reason')
   367	                                ->label('سبب الإعفاء')
   368	                                ->required()
   369	                                ->rows(3),
   370	                        ])
   371	                        ->requiresConfirmation()
   372	                        ->modalHeading('إعفاء من الاشتراك')
   373	                        ->modalDescription('هل أنت متأكد من إعفاء هذا العضو من الاشتراك؟ هذا الإجراء لا يمكن التراجع عنه.')
   374	                        ->action(function (Subscription $record, array $data) {
   375	                            try {
   376	                                $service = app(SubscriptionService::class);
   377	                                $service->waiveSubscription($record, $data['reason']);
   378	
   379	                                Notification::make()
   380	                                    ->title('تم الإعفاء بنجاح')
   381	                                    ->success()
   382	                                    ->send();
   383	                            } catch (\DomainException $e) {
   384	                                Notification::make()
   385	                                    ->title('خطأ')
   386	                                    ->body($e->getMessage())
   387	                                    ->danger()
   388	                                    ->send();
   389	                            }
   390	                        }),
   391	                ]),
   392	            ])
   393	            ->bulkActions([
   394	                Tables\Actions\BulkActionGroup::make([
   395	                    Tables\Actions\DeleteBulkAction::make(),
   396	                ]),
   397	            ]);
   398	    }
   399	
   400	    public static function infolist(Infolist $infolist): Infolist
   401	    {
   402	        return $infolist->schema([
   403	            Infolists\Components\Section::make('بيانات الاشتراك')
   404	                ->columns(3)
   405	                ->schema([
   406	                    Infolists\Components\TextEntry::make('member.name_ar')
   407	                        ->label('العضو'),
   408	                    Infolists\Components\TextEntry::make('member.membership_number')
   409	                        ->label('رقم العضوية')
   410	                        ->badge()
   411	                        ->color('info'),
   412	                    Infolists\Components\TextEntry::make('subscriptionPeriod.year')
   413	                        ->label('السنة')
   414	                        ->badge(),
   415	                    Infolists\Components\TextEntry::make('status')
   416	                        ->label('الحالة')
   417	                        ->badge()
   418	                        ->formatStateUsing(fn (SubscriptionStatus $state) => $state->label())
   419	                        ->color(fn (SubscriptionStatus $state) => $state->color())
   420	                        ->icon(fn (SubscriptionStatus $state) => $state->icon()),
   421	                    Infolists\Components\TextEntry::make('due_date')
   422	                        ->label('تاريخ الاستحقاق')
   423	                        ->date('d/m/Y'),
   424	                    Infolists\Components\TextEntry::make('paid_date')
   425	                        ->label('تاريخ الدفع')
   426	                        ->date('d/m/Y')
   427	                        ->placeholder('لم يتم الدفع'),
   428	                ]),
   429	
   430	            Infolists\Components\Section::make('التفاصيل المالية')
   431	                ->columns(4)
   432	                ->schema([
   433	                    Infolists\Components\TextEntry::make('base_amount')
   434	                        ->label('رسوم العضو')
   435	                        ->money('EGP'),
   436	                    Infolists\Components\TextEntry::make('dependents_amount')
   437	                        ->label('رسوم التابعين')
   438	                        ->money('EGP'),
   439	                    Infolists\Components\TextEntry::make('total_amount')
   440	                        ->label('الإجمالي')
   441	                        ->money('EGP'),
   442	                    Infolists\Components\TextEntry::make('discount_amount')
   443	                        ->label('الخصم')
   444	                        ->money('EGP')
   445	                        ->color('warning'),
   446	                    Infolists\Components\TextEntry::make('late_fee_amount')
   447	                        ->label('غرامة التأخير')
   448	                        ->money('EGP')
   449	                        ->color('danger'),
   450	                    Infolists\Components\TextEntry::make('net_amount')
   451	                        ->label('الصافي المطلوب')
   452	                        ->money('EGP')
   453	                        ->weight('bold')
   454	                        ->size(Infolists\Components\TextEntry\TextEntrySize::Large),
   455	                    Infolists\Components\TextEntry::make('paid_amount')
   456	                        ->label('المدفوع')
   457	                        ->money('EGP')
   458	                        ->color('success'),
   459	                    Infolists\Components\TextEntry::make('balance')
   460	                        ->label('المتبقي')
   461	                        ->money('EGP')
   462	                        ->color(fn ($state) => $state > 0 ? 'danger' : 'success')
   463	                        ->weight('bold'),
   464	                ]),
   465	
   466	            Infolists\Components\Section::make('تفاصيل التابعين')
   467	                ->visible(fn (Subscription $record) => !empty($record->dependents_breakdown))
   468	                ->schema([
   469	                    Infolists\Components\RepeatableEntry::make('dependents_breakdown')
   470	                        ->label('')
   471	                        ->schema([
   472	                            Infolists\Components\TextEntry::make('name')
   473	                                ->label('الاسم'),
   474	                            Infolists\Components\TextEntry::make('relationship')
   475	                                ->label('القرابة'),
   476	                            Infolists\Components\TextEntry::make('fee')
   477	                                ->label('الرسوم')
   478	                                ->money('EGP'),
   479	                            Infolists\Components\TextEntry::make('fee_type')
   480	                                ->label('النوع')
   481	                                ->badge(),
   482	                        ])
   483	                        ->columns(4),
   484	                ]),
   485	
   486	            Infolists\Components\Section::make('معلومات الخصم/الإعفاء')
   487	                ->visible(fn (Subscription $record) => $record->discount_amount > 0 || $record->is_waived)
   488	                ->columns(2)
   489	                ->schema([
   490	                    Infolists\Components\TextEntry::make('discount_reason')
   491	                        ->label('سبب الخصم')
   492	                        ->placeholder('—'),
   493	                    Infolists\Components\TextEntry::make('discountApprover.name')
   494	                        ->label('معتمد الخصم')
   495	                        ->placeholder('—'),
   496	                    Infolists\Components\TextEntry::make('waiver_reason')
   497	                        ->label('سبب الإعفاء')
   498	                        ->placeholder('—')
   499	                        ->visible(fn (Subscription $record) => $record->is_waived),
   500	                    Infolists\Components\TextEntry::make('waiver.name')
   501	                        ->label('معتمد الإعفاء')
   502	                        ->placeholder('—')
   503	                        ->visible(fn (Subscription $record) => $record->is_waived),
   504	                    Infolists\Components\TextEntry::make('waived_date')
   505	                        ->label('تاريخ الإعفاء')
   506	                        ->date('d/m/Y')
   507	                        ->visible(fn (Subscription $record) => $record->is_waived),
   508	                ]),
   509	
   510	            Infolists\Components\Section::make('ملاحظات')
   511	                ->visible(fn (Subscription $record) => !empty($record->notes))
   512	                ->schema([
   513	                    Infolists\Components\TextEntry::make('notes')
   514	                        ->label('')
   515	                        ->columnSpanFull(),
   516	                ]),
   517	        ]);
   518	    }
   519	
   520	    public static function getRelations(): array
   521	    {
   522	        return [
   523	            \App\Filament\Resources\SubscriptionResource\RelationManagers\PaymentsRelationManager::class,
   524	        ];
   525	    }
   526	
   527	    public static function getPages(): array
   528	    {
   529	        return [
   530	            'index'  => Pages\ListSubscriptions::route('/'),
   531	            'create' => Pages\CreateSubscription::route('/create'),
   532	            'view'   => Pages\ViewSubscription::route('/{record}'),
   533	            'edit'   => Pages\EditSubscription::route('/{record}/edit'),
   534	        ];
   535	    }
   536	
   537	    public static function getNavigationBadge(): ?string
   538	    {
   539	        $overdue = static::getModel()::where('status', SubscriptionStatus::OVERDUE)->count();
   540	        return $overdue > 0 ? (string) $overdue : null;
   541	    }
   542	
   543	    public static function getNavigationBadgeColor(): ?string
   544	    {
   545	        return 'danger';
   546	    }
   547	
   548	    public static function getEloquentQuery(): Builder
   549	    {
   550	        return parent::getEloquentQuery()
   551	            ->with(['member', 'subscriptionPeriod']);
   552	    }
   553	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [177]: app/Filament/Resources/SubscriptionResource.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [178/494]: app/Filament/Resources/SubscriptionResource/Pages/CreateSubscription.php
│ LANGUAGE: php | LINES: 48 | SIZE: 1556 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Filament\Resources\SubscriptionResource\Pages;
     4	
     5	use App\Filament\Resources\SubscriptionResource;
     6	use App\Models\Member;
     7	use App\Models\SubscriptionPeriod;
     8	use App\Services\Subscription\SubscriptionService;
     9	use Filament\Notifications\Notification;
    10	use Filament\Resources\Pages\CreateRecord;
    11	
    12	class CreateSubscription extends CreateRecord
    13	{
    14	    protected static string $resource = SubscriptionResource::class;
    15	
    16	    protected function handleRecordCreation(array $data): \Illuminate\Database\Eloquent\Model
    17	    {
    18	        $member = Member::findOrFail($data['member_id']);
    19	        $period = SubscriptionPeriod::findOrFail($data['subscription_period_id']);
    20	
    21	        try {
    22	            return app(SubscriptionService::class)->createSubscription(
    23	                member: $member,
    24	                period: $period,
    25	                discountAmount: (float) ($data['discount_amount'] ?? 0),
    26	                discountReason: $data['discount_reason'] ?? null,
    27	                notes: $data['notes'] ?? null,
    28	            );
    29	        } catch (\DomainException $e) {
    30	            Notification::make()
    31	                ->title('خطأ في إنشاء الاشتراك')
    32	                ->body($e->getMessage())
    33	                ->danger()
    34	                ->send();
    35	
    36	            $this->halt();
    37	        }
    38	    }
    39	
    40	    protected function getRedirectUrl(): string
    41	    {
    42	        return $this->getResource()::getUrl('index');
    43	    }
    44	
    45	    protected function getCreatedNotificationTitle(): ?string
    46	    {
    47	        return 'تم إنشاء الاشتراك بنجاح';
    48	    }
    49	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [178]: app/Filament/Resources/SubscriptionResource/Pages/CreateSubscription.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [179/494]: app/Filament/Resources/SubscriptionResource/Pages/EditSubscription.php
│ LANGUAGE: php | LINES: 30 | SIZE: 824 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Filament\Resources\SubscriptionResource\Pages;
     4	
     5	use App\Filament\Resources\SubscriptionResource;
     6	use Filament\Actions;
     7	use Filament\Resources\Pages\EditRecord;
     8	
     9	class EditSubscription extends EditRecord
    10	{
    11	    protected static string $resource = SubscriptionResource::class;
    12	
    13	    protected function getHeaderActions(): array
    14	    {
    15	        return [
    16	            Actions\ViewAction::make(),
    17	            Actions\DeleteAction::make()
    18	                ->visible(fn () => $this->getRecord()->paid_amount <= 0),
    19	        ];
    20	    }
    21	
    22	    protected function getRedirectUrl(): string
    23	    {
    24	        return $this->getResource()::getUrl('view', ['record' => $this->getRecord()]);
    25	    }
    26	
    27	    protected function getSavedNotificationTitle(): ?string
    28	    {
    29	        return 'تم تحديث الاشتراك بنجاح';
    30	    }
    31	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [179]: app/Filament/Resources/SubscriptionResource/Pages/EditSubscription.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [180/494]: app/Filament/Resources/SubscriptionResource/Pages/ListSubscriptions.php
│ LANGUAGE: php | LINES: 59 | SIZE: 2498 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Filament\Resources\SubscriptionResource\Pages;
     4	
     5	use App\Enums\SubscriptionStatus;
     6	use App\Filament\Resources\SubscriptionResource;
     7	use Filament\Actions;
     8	use Filament\Resources\Components\Tab;
     9	use Filament\Resources\Pages\ListRecords;
    10	use Illuminate\Database\Eloquent\Builder;
    11	
    12	class ListSubscriptions extends ListRecords
    13	{
    14	    protected static string $resource = SubscriptionResource::class;
    15	
    16	    protected function getHeaderActions(): array
    17	    {
    18	        return [
    19	            Actions\CreateAction::make()
    20	                ->label('اشتراك جديد')
    21	                ->icon('heroicon-o-plus'),
    22	        ];
    23	    }
    24	
    25	    public function getTabs(): array
    26	    {
    27	        return [
    28	            'all' => Tab::make('الكل')
    29	                ->icon('heroicon-o-list-bullet'),
    30	
    31	            'pending' => Tab::make('معلق')
    32	                ->icon('heroicon-o-clock')
    33	                ->modifyQueryUsing(fn (Builder $query) => $query->where('status', SubscriptionStatus::PENDING))
    34	                ->badge(fn () => \App\Models\Subscription::where('status', SubscriptionStatus::PENDING)->count() ?: null)
    35	                ->badgeColor('warning'),
    36	
    37	            'partial' => Tab::make('جزئي')
    38	                ->icon('heroicon-o-banknotes')
    39	                ->modifyQueryUsing(fn (Builder $query) => $query->where('status', SubscriptionStatus::PARTIAL))
    40	                ->badge(fn () => \App\Models\Subscription::where('status', SubscriptionStatus::PARTIAL)->count() ?: null)
    41	                ->badgeColor('info'),
    42	
    43	            'overdue' => Tab::make('متأخر')
    44	                ->icon('heroicon-o-exclamation-triangle')
    45	                ->modifyQueryUsing(fn (Builder $query) => $query->where('status', SubscriptionStatus::OVERDUE))
    46	                ->badge(fn () => \App\Models\Subscription::where('status', SubscriptionStatus::OVERDUE)->count() ?: null)
    47	                ->badgeColor('danger'),
    48	
    49	            'paid' => Tab::make('مدفوع')
    50	                ->icon('heroicon-o-check-circle')
    51	                ->modifyQueryUsing(fn (Builder $query) => $query->where('status', SubscriptionStatus::PAID))
    52	                ->badge(fn () => \App\Models\Subscription::where('status', SubscriptionStatus::PAID)->count() ?: null)
    53	                ->badgeColor('success'),
    54	
    55	            'waived' => Tab::make('معفى')
    56	                ->icon('heroicon-o-shield-check')
    57	                ->modifyQueryUsing(fn (Builder $query) => $query->where('status', SubscriptionStatus::WAIVED)),
    58	        ];
    59	    }
    60	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [180]: app/Filament/Resources/SubscriptionResource/Pages/ListSubscriptions.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [181/494]: app/Filament/Resources/SubscriptionResource/Pages/ViewSubscription.php
│ LANGUAGE: php | LINES: 19 | SIZE: 504 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Filament\Resources\SubscriptionResource\Pages;
     4	
     5	use App\Filament\Resources\SubscriptionResource;
     6	use Filament\Actions;
     7	use Filament\Resources\Pages\ViewRecord;
     8	
     9	class ViewSubscription extends ViewRecord
    10	{
    11	    protected static string $resource = SubscriptionResource::class;
    12	
    13	    protected function getHeaderActions(): array
    14	    {
    15	        return [
    16	            Actions\EditAction::make()
    17	                ->visible(fn () => !$this->getRecord()->status->isTerminal()),
    18	        ];
    19	    }
    20	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [181]: app/Filament/Resources/SubscriptionResource/Pages/ViewSubscription.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [182/494]: app/Filament/Resources/SubscriptionResource/RelationManagers/PaymentsRelationManager.php
│ LANGUAGE: php | LINES: 148 | SIZE: 5882 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Filament\Resources\SubscriptionResource\RelationManagers;
     4	
     5	use App\Enums\PaymentMethod;
     6	use App\Models\SubscriptionPayment;
     7	use App\Services\Subscription\SubscriptionService;
     8	use Filament\Forms;
     9	use Filament\Forms\Form;
    10	use Filament\Notifications\Notification;
    11	use Filament\Resources\RelationManagers\RelationManager;
    12	use Filament\Tables;
    13	use Filament\Tables\Table;
    14	
    15	class PaymentsRelationManager extends RelationManager
    16	{
    17	    protected static string $relationship = 'payments';
    18	    protected static ?string $title = 'المدفوعات';
    19	    protected static ?string $modelLabel = 'دفعة';
    20	
    21	    public function form(Form $form): Form
    22	    {
    23	        return $form->schema([
    24	            Forms\Components\TextInput::make('amount')
    25	                ->label('المبلغ')
    26	                ->required()
    27	                ->numeric()
    28	                ->prefix('ج.م')
    29	                ->minValue(0.01),
    30	
    31	            Forms\Components\Select::make('payment_method')
    32	                ->label('طريقة الدفع')
    33	                ->options(collect(PaymentMethod::cases())->mapWithKeys(
    34	                    fn ($m) => [$m->value => $m->label()]
    35	                ))
    36	                ->default('cash')
    37	                ->required(),
    38	
    39	            Forms\Components\DatePicker::make('payment_date')
    40	                ->label('تاريخ الدفع')
    41	                ->required()
    42	                ->default(now())
    43	                ->native(false)
    44	                ->displayFormat('d/m/Y'),
    45	
    46	            Forms\Components\TextInput::make('reference_number')
    47	                ->label('رقم المرجع')
    48	                ->placeholder('رقم الشيك أو التحويل'),
    49	
    50	            Forms\Components\Textarea::make('notes')
    51	                ->label('ملاحظات')
    52	                ->rows(2)
    53	                ->columnSpanFull(),
    54	        ]);
    55	    }
    56	
    57	    public function table(Table $table): Table
    58	    {
    59	        return $table
    60	            ->columns([
    61	                Tables\Columns\TextColumn::make('amount')
    62	                    ->label('المبلغ')
    63	                    ->money('EGP')
    64	                    ->sortable()
    65	                    ->weight('bold'),
    66	
    67	                Tables\Columns\TextColumn::make('payment_method')
    68	                    ->label('طريقة الدفع')
    69	                    ->formatStateUsing(fn (PaymentMethod $state) => $state->label())
    70	                    ->badge()
    71	                    ->icon(fn (PaymentMethod $state) => $state->icon()),
    72	
    73	                Tables\Columns\TextColumn::make('payment_date')
    74	                    ->label('التاريخ')
    75	                    ->date('d/m/Y')
    76	                    ->sortable(),
    77	
    78	                Tables\Columns\TextColumn::make('reference_number')
    79	                    ->label('المرجع')
    80	                    ->placeholder('—')
    81	                    ->toggleable(),
    82	
    83	                Tables\Columns\TextColumn::make('receipt_id')
    84	                    ->label('رقم الإيصال')
    85	                    ->placeholder('—')
    86	                    ->toggleable(),
    87	
    88	                Tables\Columns\IconColumn::make('is_reversed')
    89	                    ->label('معكوس')
    90	                    ->boolean()
    91	                    ->trueIcon('heroicon-o-x-circle')
    92	                    ->trueColor('danger')
    93	                    ->falseIcon('heroicon-o-check-circle')
    94	                    ->falseColor('success'),
    95	
    96	                Tables\Columns\TextColumn::make('creator.name')
    97	                    ->label('بواسطة')
    98	                    ->toggleable(isToggledHiddenByDefault: true),
    99	
   100	                Tables\Columns\TextColumn::make('created_at')
   101	                    ->label('تاريخ الإنشاء')
   102	                    ->dateTime('d/m/Y H:i')
   103	                    ->sortable()
   104	                    ->toggleable(isToggledHiddenByDefault: true),
   105	            ])
   106	            ->defaultSort('created_at', 'desc')
   107	            ->headerActions([
   108	                Tables\Actions\CreateAction::make()
   109	                    ->label('تسجيل دفعة')
   110	                    ->visible(fn () => $this->getOwnerRecord()->status->isPayable())
   111	                    ->after(function () {
   112	                        $this->getOwnerRecord()->recalculateBalance();
   113	                    }),
   114	            ])
   115	            ->actions([
   116	                Tables\Actions\Action::make('reverse')
   117	                    ->label('عكس')
   118	                    ->icon('heroicon-o-arrow-uturn-left')
   119	                    ->color('danger')
   120	                    ->visible(fn (SubscriptionPayment $record) => !$record->is_reversed)
   121	                    ->requiresConfirmation()
   122	                    ->modalHeading('عكس الدفعة')
   123	                    ->modalDescription('هل أنت متأكد من عكس هذه الدفعة؟ سيتم تحديث رصيد الاشتراك.')
   124	                    ->form([
   125	                        Forms\Components\Textarea::make('reason')
   126	                            ->label('سبب العكس')
   127	                            ->required()
   128	                            ->rows(2),
   129	                    ])
   130	                    ->action(function (SubscriptionPayment $record, array $data) {
   131	                        try {
   132	                            $service = app(SubscriptionService::class);
   133	                            $service->reversePayment($record, $data['reason']);
   134	
   135	                            Notification::make()
   136	                                ->title('تم عكس الدفعة')
   137	                                ->success()
   138	                                ->send();
   139	                        } catch (\DomainException $e) {
   140	                            Notification::make()
   141	                                ->title('خطأ')
   142	                                ->body($e->getMessage())
   143	                                ->danger()
   144	                                ->send();
   145	                        }
   146	                    }),
   147	            ]);
   148	    }
   149	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [182]: app/Filament/Resources/SubscriptionResource/RelationManagers/PaymentsRelationManager.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [183/494]: app/Filament/Resources/SubscriptionResource/RelationManagers/SubscriptionReceiptsRelationManager.php
│ LANGUAGE: php | LINES: 59 | SIZE: 2182 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Filament\Resources\SubscriptionResource\RelationManagers;
     4	
     5	use App\Enums\ReceiptStatus;
     6	use Filament\Resources\RelationManagers\RelationManager;
     7	use Filament\Tables;
     8	use Filament\Tables\Table;
     9	
    10	class SubscriptionReceiptsRelationManager extends RelationManager
    11	{
    12	    protected static string $relationship = 'receipts';
    13	    protected static ?string $title = 'الإيصالات';
    14	    protected static ?string $modelLabel = 'إيصال';
    15	    protected static ?string $pluralModelLabel = 'الإيصالات';
    16	
    17	    public function table(Table $table): Table
    18	    {
    19	        return $table
    20	            ->columns([
    21	                Tables\Columns\TextColumn::make('receipt_number')
    22	                    ->label('رقم الإيصال')
    23	                    ->searchable(),
    24	
    25	                Tables\Columns\TextColumn::make('receipt_date')
    26	                    ->label('التاريخ')
    27	                    ->date('Y-m-d'),
    28	
    29	                Tables\Columns\TextColumn::make('total_amount')
    30	                    ->label('الإجمالي')
    31	                    ->money('EGP'),
    32	
    33	                Tables\Columns\TextColumn::make('amount_paid')
    34	                    ->label('المدفوع')
    35	                    ->money('EGP')
    36	                    ->color('success'),
    37	
    38	                Tables\Columns\TextColumn::make('payment_method')
    39	                    ->label('طريقة الدفع')
    40	                    ->formatStateUsing(fn(string $state) => match ($state) {
    41	                        'cash' => 'نقدي',
    42	                        'bank_transfer' => 'تحويل بنكي',
    43	                        'check' => 'شيك',
    44	                        default => $state,
    45	                    }),
    46	
    47	                Tables\Columns\TextColumn::make('status')
    48	                    ->label('الحالة')
    49	                    ->badge()
    50	                    ->formatStateUsing(fn(string $state) => ReceiptStatus::tryFrom($state)?->getLabel() ?? $state)
    51	                    ->color(fn(string $state) => ReceiptStatus::tryFrom($state)?->getColor() ?? 'gray'),
    52	            ])
    53	            ->defaultSort('created_at', 'desc');
    54	    }
    55	
    56	    public function isReadOnly(): bool
    57	    {
    58	        return true;
    59	    }
    60	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [183]: app/Filament/Resources/SubscriptionResource/RelationManagers/SubscriptionReceiptsRelationManager.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [184/494]: app/Filament/Resources/UserResource.php
│ LANGUAGE: php | LINES: 203 | SIZE: 7825 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Filament\Resources;
     4	
     5	use App\Filament\Resources\UserResource\Pages;
     6	use App\Filament\Resources\UserResource\RelationManagers;
     7	use App\Models\User;
     8	use Filament\Forms;
     9	use Filament\Forms\Form;
    10	use Filament\Resources\Resource;
    11	use Filament\Tables;
    12	use Filament\Tables\Table;
    13	use Illuminate\Support\Facades\Hash;
    14	use Illuminate\Database\Eloquent\Builder;
    15	
    16	class UserResource extends Resource
    17	{
    18	    protected static ?string $model = User::class;
    19	
    20	    protected static ?string $navigationIcon = 'heroicon-o-users';
    21	
    22	    protected static ?string $navigationGroup = 'إدارة النظام';
    23	
    24	    protected static ?string $navigationLabel = 'المستخدمين';
    25	
    26	    protected static ?string $modelLabel = 'مستخدم';
    27	
    28	    protected static ?string $pluralModelLabel = 'المستخدمين';
    29	
    30	    protected static ?int $navigationSort = 1;
    31	
    32	    public static function form(Form $form): Form
    33	    {
    34	        return $form->schema([
    35	            Forms\Components\Section::make('البيانات الأساسية')
    36	                ->schema([
    37	                    Forms\Components\TextInput::make('name')
    38	                        ->label('الاسم')
    39	                        ->required()
    40	                        ->maxLength(255),
    41	
    42	                    Forms\Components\TextInput::make('email')
    43	                        ->label('البريد الإلكتروني')
    44	                        ->email()
    45	                        ->required()
    46	                        ->unique(ignoreRecord: true)
    47	                        ->maxLength(255),
    48	
    49	                    Forms\Components\TextInput::make('password')
    50	                        ->label('كلمة المرور')
    51	                        ->password()
    52	                        ->dehydrateStateUsing(fn (string $state): string => Hash::make($state))
    53	                        ->dehydrated(fn (?string $state): bool => filled($state))
    54	                        ->required(fn (string $operation): bool => $operation === 'create')
    55	                        ->minLength(8)
    56	                        ->maxLength(255),
    57	
    58	                    Forms\Components\TextInput::make('password_confirmation')
    59	                        ->label('تأكيد كلمة المرور')
    60	                        ->password()
    61	                        ->same('password')
    62	                        ->requiredWith('password')
    63	                        ->dehydrated(false),
    64	                ])->columns(2),
    65	
    66	            Forms\Components\Section::make('بيانات إضافية')
    67	                ->schema([
    68	                    Forms\Components\TextInput::make('phone')
    69	                        ->label('رقم الهاتف')
    70	                        ->tel()
    71	                        ->maxLength(20),
    72	
    73	                    Forms\Components\TextInput::make('job_title')
    74	                        ->label('المسمى الوظيفي')
    75	                        ->maxLength(255),
    76	
    77	                    Forms\Components\Toggle::make('is_active')
    78	                        ->label('نشط')
    79	                        ->default(true),
    80	
    81	                    Forms\Components\SpatieMediaLibraryFileUpload::make('avatar')
    82	                        ->label('الصورة الشخصية')
    83	                        ->collection('avatar')
    84	                        ->image()
    85	                        ->imageResizeMode('cover')
    86	                        ->imageCropAspectRatio('1:1')
    87	                        ->imageResizeTargetWidth('200')
    88	                        ->imageResizeTargetHeight('200'),
    89	                ])->columns(2),
    90	
    91	            Forms\Components\Section::make('الصلاحيات')
    92	                ->schema([
    93	                    Forms\Components\Select::make('roles')
    94	                        ->label('الأدوار')
    95	                        ->multiple()
    96	                        ->relationship('roles', 'name')
    97	                        ->preload()
    98	                        ->searchable(),
    99	                ]),
   100	        ]);
   101	    }
   102	
   103	    public static function table(Table $table): Table
   104	    {
   105	        return $table
   106	            ->columns([
   107	                Tables\Columns\SpatieMediaLibraryImageColumn::make('avatar')
   108	                    ->label('')
   109	                    ->collection('avatar')
   110	                    ->circular()
   111	                    ->defaultImageUrl(fn ($record) => 'https://ui-avatars.com/api/?name=' . urlencode($record->name) . '&background=random'),
   112	
   113	                Tables\Columns\TextColumn::make('name')
   114	                    ->label('الاسم')
   115	                    ->searchable()
   116	                    ->sortable(),
   117	
   118	                Tables\Columns\TextColumn::make('email')
   119	                    ->label('البريد الإلكتروني')
   120	                    ->searchable()
   121	                    ->sortable(),
   122	
   123	                Tables\Columns\TextColumn::make('phone')
   124	                    ->label('الهاتف')
   125	                    ->searchable(),
   126	
   127	                Tables\Columns\TextColumn::make('job_title')
   128	                    ->label('المسمى الوظيفي')
   129	                    ->toggleable(),
   130	
   131	                Tables\Columns\TextColumn::make('roles.name')
   132	                    ->label('الأدوار')
   133	                    ->badge()
   134	                    ->separator(','),
   135	
   136	                Tables\Columns\IconColumn::make('is_active')
   137	                    ->label('نشط')
   138	                    ->boolean(),
   139	
   140	                Tables\Columns\TextColumn::make('last_login_at')
   141	                    ->label('آخر دخول')
   142	                    ->dateTime('d/m/Y H:i')
   143	                    ->sortable()
   144	                    ->toggleable(),
   145	
   146	                Tables\Columns\TextColumn::make('created_at')
   147	                    ->label('تاريخ الإنشاء')
   148	                    ->dateTime('d/m/Y')
   149	                    ->sortable()
   150	                    ->toggleable(isToggledHiddenByDefault: true),
   151	            ])
   152	            ->defaultSort('name')
   153	            ->filters([
   154	                Tables\Filters\TernaryFilter::make('is_active')
   155	                    ->label('الحالة')
   156	                    ->trueLabel('نشط')
   157	                    ->falseLabel('غير نشط'),
   158	
   159	                Tables\Filters\SelectFilter::make('roles')
   160	                    ->label('الدور')
   161	                    ->relationship('roles', 'name')
   162	                    ->multiple()
   163	                    ->preload(),
   164	            ])
   165	            ->actions([
   166	                Tables\Actions\ViewAction::make(),
   167	                Tables\Actions\EditAction::make(),
   168	                Tables\Actions\Action::make('toggleActive')
   169	                    ->label(fn (User $record) => $record->is_active ? 'تعطيل' : 'تفعيل')
   170	                    ->icon(fn (User $record) => $record->is_active ? 'heroicon-o-x-circle' : 'heroicon-o-check-circle')
   171	                    ->color(fn (User $record) => $record->is_active ? 'danger' : 'success')
   172	                    ->requiresConfirmation()
   173	                    ->action(fn (User $record) => $record->update(['is_active' => !$record->is_active]))
   174	                    ->hidden(fn (User $record) => $record->id === auth()->id()),
   175	            ])
   176	            ->bulkActions([
   177	                Tables\Actions\BulkActionGroup::make([
   178	                    Tables\Actions\DeleteBulkAction::make(),
   179	                ]),
   180	            ]);
   181	    }
   182	
   183	    public static function getRelations(): array
   184	    {
   185	        return [
   186	            RelationManagers\ActivityLogRelationManager::class,
   187	        ];
   188	    }
   189	
   190	    public static function getPages(): array
   191	    {
   192	        return [
   193	            'index' => Pages\ListUsers::route('/'),
   194	            'create' => Pages\CreateUser::route('/create'),
   195	            'view' => Pages\ViewUser::route('/{record}'),
   196	            'edit' => Pages\EditUser::route('/{record}/edit'),
   197	        ];
   198	    }
   199	
   200	    public static function getEloquentQuery(): Builder
   201	    {
   202	        return parent::getEloquentQuery()->with('roles');
   203	    }
   204	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [184]: app/Filament/Resources/UserResource.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [185/494]: app/Filament/Resources/UserResource/Pages/CreateUser.php
│ LANGUAGE: php | LINES: 15 | SIZE: 363 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Filament\Resources\UserResource\Pages;
     4	
     5	use App\Filament\Resources\UserResource;
     6	use Filament\Resources\Pages\CreateRecord;
     7	
     8	class CreateUser extends CreateRecord
     9	{
    10	    protected static string $resource = UserResource::class;
    11	
    12	    protected function getRedirectUrl(): string
    13	    {
    14	        return $this->getResource()::getUrl('index');
    15	    }
    16	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [185]: app/Filament/Resources/UserResource/Pages/CreateUser.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [186/494]: app/Filament/Resources/UserResource/Pages/EditUser.php
│ LANGUAGE: php | LINES: 25 | SIZE: 621 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Filament\Resources\UserResource\Pages;
     4	
     5	use App\Filament\Resources\UserResource;
     6	use Filament\Actions;
     7	use Filament\Resources\Pages\EditRecord;
     8	
     9	class EditUser extends EditRecord
    10	{
    11	    protected static string $resource = UserResource::class;
    12	
    13	    protected function getHeaderActions(): array
    14	    {
    15	        return [
    16	            Actions\ViewAction::make(),
    17	            Actions\DeleteAction::make()
    18	                ->hidden(fn () => $this->record->id === auth()->id()),
    19	        ];
    20	    }
    21	
    22	    protected function getRedirectUrl(): string
    23	    {
    24	        return $this->getResource()::getUrl('index');
    25	    }
    26	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [186]: app/Filament/Resources/UserResource/Pages/EditUser.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [187/494]: app/Filament/Resources/UserResource/Pages/ListUsers.php
│ LANGUAGE: php | LINES: 18 | SIZE: 399 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Filament\Resources\UserResource\Pages;
     4	
     5	use App\Filament\Resources\UserResource;
     6	use Filament\Actions;
     7	use Filament\Resources\Pages\ListRecords;
     8	
     9	class ListUsers extends ListRecords
    10	{
    11	    protected static string $resource = UserResource::class;
    12	
    13	    protected function getHeaderActions(): array
    14	    {
    15	        return [
    16	            Actions\CreateAction::make(),
    17	        ];
    18	    }
    19	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [187]: app/Filament/Resources/UserResource/Pages/ListUsers.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [188/494]: app/Filament/Resources/UserResource/Pages/ViewUser.php
│ LANGUAGE: php | LINES: 18 | SIZE: 394 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Filament\Resources\UserResource\Pages;
     4	
     5	use App\Filament\Resources\UserResource;
     6	use Filament\Actions;
     7	use Filament\Resources\Pages\ViewRecord;
     8	
     9	class ViewUser extends ViewRecord
    10	{
    11	    protected static string $resource = UserResource::class;
    12	
    13	    protected function getHeaderActions(): array
    14	    {
    15	        return [
    16	            Actions\EditAction::make(),
    17	        ];
    18	    }
    19	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [188]: app/Filament/Resources/UserResource/Pages/ViewUser.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [189/494]: app/Filament/Resources/UserResource/RelationManagers/ActivityLogRelationManager.php
│ LANGUAGE: php | LINES: 60 | SIZE: 2132 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Filament\Resources\UserResource\RelationManagers;
     4	
     5	use Filament\Resources\RelationManagers\RelationManager;
     6	use Filament\Tables;
     7	use Filament\Tables\Table;
     8	use Spatie\Activitylog\Models\Activity;
     9	
    10	class ActivityLogRelationManager extends RelationManager
    11	{
    12	    protected static string $relationship = 'actions';
    13	
    14	    protected static ?string $title = 'سجل النشاط';
    15	
    16	    protected static ?string $modelLabel = 'نشاط';
    17	
    18	    public static function getModelLabel(): string
    19	    {
    20	        return 'نشاط';
    21	    }
    22	
    23	    public function table(Table $table): Table
    24	    {
    25	        return $table
    26	            ->columns([
    27	                Tables\Columns\TextColumn::make('description')
    28	                    ->label('الوصف')
    29	                    ->wrap()
    30	                    ->limit(80),
    31	
    32	                Tables\Columns\TextColumn::make('subject_type')
    33	                    ->label('النوع')
    34	                    ->formatStateUsing(fn (?string $state) => $state ? class_basename($state) : '—')
    35	                    ->badge(),
    36	
    37	                Tables\Columns\TextColumn::make('properties')
    38	                    ->label('التفاصيل')
    39	                    ->formatStateUsing(function ($state) {
    40	                        if (empty($state)) return '—';
    41	                        $props = is_string($state) ? json_decode($state, true) : $state;
    42	                        if (isset($props['attributes'])) {
    43	                            return collect($props['attributes'])
    44	                                ->map(fn ($v, $k) => "{$k}: {$v}")
    45	                                ->take(3)
    46	                                ->join(', ');
    47	                        }
    48	                        return json_encode($props, JSON_UNESCAPED_UNICODE);
    49	                    })
    50	                    ->wrap()
    51	                    ->toggleable(),
    52	
    53	                Tables\Columns\TextColumn::make('created_at')
    54	                    ->label('التاريخ')
    55	                    ->dateTime('d/m/Y H:i:s')
    56	                    ->sortable(),
    57	            ])
    58	            ->defaultSort('created_at', 'desc')
    59	            ->paginated([10, 25, 50]);
    60	    }
    61	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [189]: app/Filament/Resources/UserResource/RelationManagers/ActivityLogRelationManager.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [190/494]: app/Filament/Resources/ViolationResource.php
│ LANGUAGE: php | LINES: 597 | SIZE: 28780 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	declare(strict_types=1);
     4	
     5	namespace App\Filament\Resources;
     6	
     7	use App\Enums\ViolationSeverity;
     8	use App\Enums\ViolationStatus;
     9	use App\Filament\Resources\ViolationResource\Pages;
    10	use App\Filament\Resources\ViolationResource\RelationManagers;
    11	use App\Models\Violation;
    12	use App\Models\ViolationType;
    13	use Filament\Forms;
    14	use Filament\Forms\Form;
    15	use Filament\Infolists;
    16	use Filament\Infolists\Infolist;
    17	use Filament\Resources\Resource;
    18	use Filament\Tables;
    19	use Filament\Tables\Table;
    20	use Illuminate\Database\Eloquent\Builder;
    21	
    22	class ViolationResource extends Resource
    23	{
    24	    protected static ?string $model = Violation::class;
    25	
    26	    protected static ?string $navigationIcon = 'heroicon-o-exclamation-triangle';
    27	
    28	    protected static ?string $navigationGroup = 'الانضباط';
    29	
    30	    protected static ?string $navigationLabel = 'المخالفات';
    31	
    32	    protected static ?string $modelLabel = 'مخالفة';
    33	
    34	    protected static ?string $pluralModelLabel = 'المخالفات';
    35	
    36	    protected static ?int $navigationSort = 1;
    37	
    38	    protected static ?string $recordTitleAttribute = 'violation_number';
    39	
    40	    public static function getNavigationBadge(): ?string
    41	    {
    42	        return (string) static::getModel()::whereIn('status', [
    43	            ViolationStatus::REPORTED,
    44	            ViolationStatus::UNDER_INVESTIGATION,
    45	            ViolationStatus::PENDING_BOARD_REVIEW,
    46	        ])->count() ?: null;
    47	    }
    48	
    49	    public static function getNavigationBadgeColor(): string|array|null
    50	    {
    51	        return 'danger';
    52	    }
    53	
    54	    public static function form(Form $form): Form
    55	    {
    56	        return $form->schema([
    57	            Forms\Components\Group::make()
    58	                ->schema([
    59	                    Forms\Components\Section::make('بيانات المخالفة')
    60	                        ->schema([
    61	                            Forms\Components\TextInput::make('violation_number')
    62	                                ->label('رقم المخالفة')
    63	                                ->disabled()
    64	                                ->visibleOn('edit'),
    65	
    66	                            Forms\Components\Select::make('member_id')
    67	                                ->label('العضو')
    68	                                ->relationship('member', 'full_name_ar')
    69	                                ->searchable()
    70	                                ->preload()
    71	                                ->required()
    72	                                ->getSearchResultsUsing(function (string $search): array {
    73	                                    return \App\Models\Member::query()
    74	                                        ->where('full_name_ar', 'like', "%{$search}%")
    75	                                        ->orWhere('membership_number', 'like', "%{$search}%")
    76	                                        ->orWhere('national_id', 'like', "%{$search}%")
    77	                                        ->limit(50)
    78	                                        ->pluck('full_name_ar', 'id')
    79	                                        ->toArray();
    80	                                })
    81	                                ->getOptionLabelUsing(fn ($value): ?string =>
    82	                                    \App\Models\Member::find($value)?->full_name_ar
    83	                                ),
    84	
    85	                            Forms\Components\Select::make('violation_type_id')
    86	                                ->label('نوع المخالفة')
    87	                                ->relationship('violationType', 'name_ar', fn (Builder $query) => $query->where('is_active', true))
    88	                                ->searchable()
    89	                                ->preload()
    90	                                ->reactive()
    91	                                ->afterStateUpdated(function (Forms\Set $set, $state) {
    92	                                    if ($state) {
    93	                                        $type = ViolationType::find($state);
    94	                                        if ($type) {
    95	                                            $set('severity', $type->default_severity);
    96	                                        }
    97	                                    }
    98	                                }),
    99	
   100	                            Forms\Components\TextInput::make('title')
   101	                                ->label('عنوان المخالفة')
   102	                                ->required()
   103	                                ->maxLength(255)
   104	                                ->columnSpanFull(),
   105	
   106	                            Forms\Components\Select::make('severity')
   107	                                ->label('درجة الخطورة')
   108	                                ->options(ViolationSeverity::toFilamentOptions())
   109	                                ->required(),
   110	
   111	                            Forms\Components\DateTimePicker::make('violation_date')
   112	                                ->label('تاريخ المخالفة')
   113	                                ->required()
   114	                                ->default(now()),
   115	
   116	                            Forms\Components\TextInput::make('violation_location')
   117	                                ->label('مكان المخالفة')
   118	                                ->maxLength(255),
   119	                        ])
   120	                        ->columns(2),
   121	
   122	                    Forms\Components\Section::make('التفاصيل')
   123	                        ->schema([
   124	                            Forms\Components\RichEditor::make('description')
   125	                                ->label('وصف المخالفة')
   126	                                ->columnSpanFull(),
   127	
   128	                            Forms\Components\Textarea::make('witnesses')
   129	                                ->label('الشهود')
   130	                                ->rows(2)
   131	                                ->columnSpanFull(),
   132	
   133	                            Forms\Components\Textarea::make('evidence_notes')
   134	                                ->label('ملاحظات الأدلة')
   135	                                ->rows(2)
   136	                                ->columnSpanFull(),
   137	
   138	                            Forms\Components\Textarea::make('notes')
   139	                                ->label('ملاحظات عامة')
   140	                                ->rows(2)
   141	                                ->columnSpanFull(),
   142	                        ]),
   143	                ])
   144	                ->columnSpan(['lg' => 2]),
   145	
   146	            Forms\Components\Group::make()
   147	                ->schema([
   148	                    Forms\Components\Section::make('الحالة')
   149	                        ->schema([
   150	                            Forms\Components\Select::make('status')
   151	                                ->label('حالة المخالفة')
   152	                                ->options(ViolationStatus::toFilamentOptions())
   153	                                ->disabled()
   154	                                ->default(ViolationStatus::REPORTED)
   155	                                ->visibleOn('edit'),
   156	
   157	                            Forms\Components\Placeholder::make('reported_by_name')
   158	                                ->label('تم الإبلاغ بواسطة')
   159	                                ->content(fn (?Violation $record) => $record?->reporter?->name ?? '-')
   160	                                ->visibleOn('edit'),
   161	
   162	                            Forms\Components\Placeholder::make('reported_at_display')
   163	                                ->label('تاريخ الإبلاغ')
   164	                                ->content(fn (?Violation $record) => $record?->reported_at?->format('d/m/Y H:i') ?? '-')
   165	                                ->visibleOn('edit'),
   166	
   167	                            Forms\Components\Placeholder::make('created_at_display')
   168	                                ->label('تاريخ الإنشاء')
   169	                                ->content(fn (?Violation $record) => $record?->created_at?->diffForHumans() ?? '-')
   170	                                ->visibleOn('edit'),
   171	                        ]),
   172	
   173	                    Forms\Components\Section::make('التحقيق')
   174	                        ->schema([
   175	                            Forms\Components\Textarea::make('investigation_notes')
   176	                                ->label('ملاحظات التحقيق')
   177	                                ->rows(3),
   178	
   179	                            Forms\Components\Textarea::make('investigation_findings')
   180	                                ->label('نتائج التحقيق')
   181	                                ->rows(3),
   182	                        ])
   183	                        ->visibleOn('edit')
   184	                        ->visible(fn (?Violation $record) => $record && in_array($record->status, [
   185	                            ViolationStatus::UNDER_INVESTIGATION,
   186	                            ViolationStatus::INVESTIGATION_COMPLETE,
   187	                            ViolationStatus::PENDING_BOARD_REVIEW,
   188	                            ViolationStatus::BOARD_REVIEWED,
   189	                            ViolationStatus::PENALTY_IMPOSED,
   190	                            ViolationStatus::RESOLVED,
   191	                        ])),
   192	                ])
   193	                ->columnSpan(['lg' => 1]),
   194	        ])->columns(3);
   195	    }
   196	
   197	    public static function infolist(Infolist $infolist): Infolist
   198	    {
   199	        return $infolist->schema([
   200	            Infolists\Components\Group::make()
   201	                ->schema([
   202	                    Infolists\Components\Section::make('بيانات المخالفة')
   203	                        ->schema([
   204	                            Infolists\Components\TextEntry::make('violation_number')
   205	                                ->label('رقم المخالفة')
   206	                                ->badge()
   207	                                ->color('danger'),
   208	
   209	                            Infolists\Components\TextEntry::make('member.full_name_ar')
   210	                                ->label('العضو')
   211	                                ->url(fn (Violation $record) => MemberResource::getUrl('view', ['record' => $record->member_id])),
   212	
   213	                            Infolists\Components\TextEntry::make('member.membership_number')
   214	                                ->label('رقم العضوية'),
   215	
   216	                            Infolists\Components\TextEntry::make('violationType.name_ar')
   217	                                ->label('نوع المخالفة')
   218	                                ->default('—'),
   219	
   220	                            Infolists\Components\TextEntry::make('title')
   221	                                ->label('العنوان')
   222	                                ->columnSpanFull(),
   223	
   224	                            Infolists\Components\TextEntry::make('severity')
   225	                                ->label('الخطورة')
   226	                                ->badge()
   227	                                ->formatStateUsing(fn ($state) => $state instanceof ViolationSeverity ? $state->getLabel() : $state)
   228	                                ->color(fn ($state) => $state instanceof ViolationSeverity ? $state->getColor() : 'gray'),
   229	
   230	                            Infolists\Components\TextEntry::make('status')
   231	                                ->label('الحالة')
   232	                                ->badge()
   233	                                ->formatStateUsing(fn ($state) => $state instanceof ViolationStatus ? $state->getLabel() : $state)
   234	                                ->color(fn ($state) => $state instanceof ViolationStatus ? $state->getColor() : 'gray'),
   235	
   236	                            Infolists\Components\TextEntry::make('violation_date')
   237	                                ->label('تاريخ المخالفة')
   238	                                ->dateTime('d/m/Y H:i'),
   239	
   240	                            Infolists\Components\TextEntry::make('violation_location')
   241	                                ->label('المكان')
   242	                                ->default('—'),
   243	                        ])
   244	                        ->columns(2),
   245	
   246	                    Infolists\Components\Section::make('التفاصيل')
   247	                        ->schema([
   248	                            Infolists\Components\TextEntry::make('description')
   249	                                ->label('الوصف')
   250	                                ->html()
   251	                                ->columnSpanFull(),
   252	
   253	                            Infolists\Components\TextEntry::make('witnesses')
   254	                                ->label('الشهود')
   255	                                ->default('—')
   256	                                ->columnSpanFull(),
   257	
   258	                            Infolists\Components\TextEntry::make('evidence_notes')
   259	                                ->label('ملاحظات الأدلة')
   260	                                ->default('—')
   261	                                ->columnSpanFull(),
   262	
   263	                            Infolists\Components\TextEntry::make('notes')
   264	                                ->label('ملاحظات عامة')
   265	                                ->default('—')
   266	                                ->columnSpanFull(),
   267	                        ]),
   268	
   269	                    Infolists\Components\Section::make('التحقيق')
   270	                        ->schema([
   271	                            Infolists\Components\TextEntry::make('investigator.name')
   272	                                ->label('المحقق')
   273	                                ->default('—'),
   274	
   275	                            Infolists\Components\TextEntry::make('investigation_started_at')
   276	                                ->label('بداية التحقيق')
   277	                                ->dateTime('d/m/Y H:i')
   278	                                ->default('—'),
   279	
   280	                            Infolists\Components\TextEntry::make('investigation_completed_at')
   281	                                ->label('نهاية التحقيق')
   282	                                ->dateTime('d/m/Y H:i')
   283	                                ->default('—'),
   284	
   285	                            Infolists\Components\TextEntry::make('investigation_notes')
   286	                                ->label('ملاحظات التحقيق')
   287	                                ->default('—')
   288	                                ->columnSpanFull(),
   289	
   290	                            Infolists\Components\TextEntry::make('investigation_findings')
   291	                                ->label('نتائج التحقيق')
   292	                                ->default('—')
   293	                                ->columnSpanFull(),
   294	                        ])
   295	                        ->visible(fn (Violation $record) => $record->investigation_started_at !== null),
   296	
   297	                    Infolists\Components\Section::make('قرار المجلس')
   298	                        ->schema([
   299	                            Infolists\Components\TextEntry::make('board_decision_date')
   300	                                ->label('تاريخ القرار')
   301	                                ->date('d/m/Y')
   302	                                ->default('—'),
   303	
   304	                            Infolists\Components\TextEntry::make('board_decision_reference')
   305	                                ->label('رقم القرار')
   306	                                ->default('—'),
   307	
   308	                            Infolists\Components\TextEntry::make('board_decision_notes')
   309	                                ->label('ملاحظات القرار')
   310	                                ->default('—')
   311	                                ->columnSpanFull(),
   312	                        ])
   313	                        ->visible(fn (Violation $record) => $record->escalated_to_board),
   314	                ])
   315	                ->columnSpan(['lg' => 2]),
   316	
   317	            Infolists\Components\Group::make()
   318	                ->schema([
   319	                    Infolists\Components\Section::make('المعلومات الإدارية')
   320	                        ->schema([
   321	                            Infolists\Components\TextEntry::make('reporter.name')
   322	                                ->label('تم الإبلاغ بواسطة'),
   323	
   324	                            Infolists\Components\TextEntry::make('reported_at')
   325	                                ->label('تاريخ الإبلاغ')
   326	                                ->dateTime('d/m/Y H:i'),
   327	
   328	                            Infolists\Components\TextEntry::make('resolved_at')
   329	                                ->label('تاريخ الحل')
   330	                                ->dateTime('d/m/Y H:i')
   331	                                ->default('—'),
   332	
   333	                            Infolists\Components\TextEntry::make('created_at')
   334	                                ->label('تاريخ الإنشاء')
   335	                                ->dateTime('d/m/Y H:i'),
   336	
   337	                            Infolists\Components\TextEntry::make('updated_at')
   338	                                ->label('آخر تحديث')
   339	                                ->dateTime('d/m/Y H:i'),
   340	                        ]),
   341	                ])
   342	                ->columnSpan(['lg' => 1]),
   343	        ])->columns(3);
   344	    }
   345	
   346	    public static function table(Table $table): Table
   347	    {
   348	        return $table
   349	            ->columns([
   350	                Tables\Columns\TextColumn::make('violation_number')
   351	                    ->label('رقم المخالفة')
   352	                    ->searchable()
   353	                    ->sortable()
   354	                    ->copyable()
   355	                    ->weight('bold'),
   356	
   357	                Tables\Columns\TextColumn::make('member.full_name_ar')
   358	                    ->label('العضو')
   359	                    ->searchable()
   360	                    ->sortable()
   361	                    ->wrap(),
   362	
   363	                Tables\Columns\TextColumn::make('member.membership_number')
   364	                    ->label('رقم العضوية')
   365	                    ->searchable()
   366	                    ->sortable(),
   367	
   368	                Tables\Columns\TextColumn::make('violationType.name_ar')
   369	                    ->label('النوع')
   370	                    ->sortable()
   371	                    ->default('—'),
   372	
   373	                Tables\Columns\TextColumn::make('title')
   374	                    ->label('العنوان')
   375	                    ->limit(40)
   376	                    ->tooltip(fn ($record) => $record->title)
   377	                    ->searchable(),
   378	
   379	                Tables\Columns\TextColumn::make('severity')
   380	                    ->label('الخطورة')
   381	                    ->badge()
   382	                    ->formatStateUsing(fn ($state) => $state instanceof ViolationSeverity ? $state->getLabel() : $state)
   383	                    ->color(fn ($state) => $state instanceof ViolationSeverity ? $state->getColor() : 'gray')
   384	                    ->sortable(),
   385	
   386	                Tables\Columns\TextColumn::make('status')
   387	                    ->label('الحالة')
   388	                    ->badge()
   389	                    ->formatStateUsing(fn ($state) => $state instanceof ViolationStatus ? $state->getLabel() : $state)
   390	                    ->color(fn ($state) => $state instanceof ViolationStatus ? $state->getColor() : 'gray')
   391	                    ->sortable(),
   392	
   393	                Tables\Columns\TextColumn::make('violation_date')
   394	                    ->label('تاريخ المخالفة')
   395	                    ->dateTime('d/m/Y')
   396	                    ->sortable(),
   397	
   398	                Tables\Columns\TextColumn::make('penalties_count')
   399	                    ->label('العقوبات')
   400	                    ->counts('penalties')
   401	                    ->badge()
   402	                    ->color('warning'),
   403	            ])
   404	            ->defaultSort('created_at', 'desc')
   405	            ->filters([
   406	                Tables\Filters\SelectFilter::make('status')
   407	                    ->label('الحالة')
   408	                    ->options(ViolationStatus::toFilamentOptions())
   409	                    ->multiple(),
   410	
   411	                Tables\Filters\SelectFilter::make('severity')
   412	                    ->label('الخطورة')
   413	                    ->options(ViolationSeverity::toFilamentOptions())
   414	                    ->multiple(),
   415	
   416	                Tables\Filters\SelectFilter::make('violation_type_id')
   417	                    ->label('نوع المخالفة')
   418	                    ->relationship('violationType', 'name_ar'),
   419	
   420	                Tables\Filters\Filter::make('violation_date')
   421	                    ->form([
   422	                        Forms\Components\DatePicker::make('from')
   423	                            ->label('من تاريخ'),
   424	                        Forms\Components\DatePicker::make('until')
   425	                            ->label('إلى تاريخ'),
   426	                    ])
   427	                    ->query(function (Builder $query, array $data): Builder {
   428	                        return $query
   429	                            ->when($data['from'], fn (Builder $q, $date) => $q->whereDate('violation_date', '>=', $date))
   430	                            ->when($data['until'], fn (Builder $q, $date) => $q->whereDate('violation_date', '<=', $date));
   431	                    }),
   432	
   433	                Tables\Filters\TernaryFilter::make('escalated_to_board')
   434	                    ->label('معروض على المجلس'),
   435	
   436	                Tables\Filters\Filter::make('unresolved')
   437	                    ->label('غير محلولة')
   438	                    ->query(fn (Builder $query) => $query->whereNotIn('status', [
   439	                        ViolationStatus::DISMISSED,
   440	                        ViolationStatus::RESOLVED,
   441	                        ViolationStatus::APPEALED_OVERTURNED,
   442	                    ]))
   443	                    ->toggle(),
   444	            ])
   445	            ->actions([
   446	                Tables\Actions\ActionGroup::make([
   447	                    Tables\Actions\ViewAction::make(),
   448	                    Tables\Actions\EditAction::make(),
   449	
   450	                    Tables\Actions\Action::make('start_investigation')
   451	                        ->label('بدء التحقيق')
   452	                        ->icon('heroicon-o-magnifying-glass')
   453	                        ->color('info')
   454	                        ->requiresConfirmation()
   455	                        ->modalHeading('بدء التحقيق في المخالفة')
   456	                        ->form([
   457	                            Forms\Components\Textarea::make('investigation_notes')
   458	                                ->label('ملاحظات أولية')
   459	                                ->rows(3),
   460	                        ])
   461	                        ->action(function (Violation $record, array $data) {
   462	                            app(\App\Services\Disciplinary\ViolationService::class)
   463	                                ->startInvestigation($record, $data);
   464	                        })
   465	                        ->visible(fn (Violation $record) => $record->status === ViolationStatus::REPORTED),
   466	
   467	                    Tables\Actions\Action::make('complete_investigation')
   468	                        ->label('إنهاء التحقيق')
   469	                        ->icon('heroicon-o-check-circle')
   470	                        ->color('success')
   471	                        ->form([
   472	                            Forms\Components\Textarea::make('investigation_findings')
   473	                                ->label('نتائج التحقيق')
   474	                                ->required()
   475	                                ->rows(4),
   476	
   477	                            Forms\Components\Textarea::make('investigation_notes')
   478	                                ->label('ملاحظات إضافية')
   479	                                ->rows(3),
   480	
   481	                            Forms\Components\Select::make('severity')
   482	                                ->label('إعادة تقييم الخطورة')
   483	                                ->options(ViolationSeverity::toFilamentOptions())
   484	                                ->helperText('اتركه فارغاً للإبقاء على التقييم الحالي'),
   485	                        ])
   486	                        ->action(function (Violation $record, array $data) {
   487	                            app(\App\Services\Disciplinary\ViolationService::class)
   488	                                ->completeInvestigation($record, $data);
   489	                        })
   490	                        ->visible(fn (Violation $record) => $record->status === ViolationStatus::UNDER_INVESTIGATION),
   491	
   492	                    Tables\Actions\Action::make('escalate_to_board')
   493	                        ->label('عرض على المجلس')
   494	                        ->icon('heroicon-o-building-library')
   495	                        ->color('warning')
   496	                        ->requiresConfirmation()
   497	                        ->form([
   498	                            Forms\Components\Textarea::make('escalation_reason')
   499	                                ->label('سبب العرض على المجلس')
   500	                                ->rows(3),
   501	                        ])
   502	                        ->action(function (Violation $record, array $data) {
   503	                            app(\App\Services\Disciplinary\ViolationService::class)
   504	                                ->escalateToBoard($record, $data);
   505	                        })
   506	                        ->visible(fn (Violation $record) => in_array($record->status, [
   507	                            ViolationStatus::INVESTIGATION_COMPLETE,
   508	                            ViolationStatus::REPORTED,
   509	                        ])),
   510	
   511	                    Tables\Actions\Action::make('board_decision')
   512	                        ->label('تسجيل قرار المجلس')
   513	                        ->icon('heroicon-o-clipboard-document-check')
   514	                        ->color('primary')
   515	                        ->form([
   516	                            Forms\Components\Toggle::make('board_approved')
   517	                                ->label('موافقة المجلس')
   518	                                ->required(),
   519	
   520	                            Forms\Components\DatePicker::make('board_decision_date')
   521	                                ->label('تاريخ القرار')
   522	                                ->default(now()),
   523	
   524	                            Forms\Components\TextInput::make('board_decision_reference')
   525	                                ->label('رقم مرجع القرار')
   526	                                ->maxLength(100),
   527	
   528	                            Forms\Components\Textarea::make('board_decision_notes')
   529	                                ->label('ملاحظات القرار')
   530	                                ->rows(3),
   531	                        ])
   532	                        ->action(function (Violation $record, array $data) {
   533	                            app(\App\Services\Disciplinary\ViolationService::class)
   534	                                ->recordBoardDecision($record, $data);
   535	                        })
   536	                        ->visible(fn (Violation $record) => $record->status === ViolationStatus::PENDING_BOARD_REVIEW),
   537	
   538	                    Tables\Actions\Action::make('dismiss')
   539	                        ->label('رفض المخالفة')
   540	                        ->icon('heroicon-o-x-circle')
   541	                        ->color('gray')
   542	                        ->requiresConfirmation()
   543	                        ->modalHeading('رفض المخالفة')
   544	                        ->modalDescription('هل أنت متأكد من رفض هذه المخالفة؟ لن يتم اتخاذ أي إجراء.')
   545	                        ->form([
   546	                            Forms\Components\Textarea::make('dismissal_reason')
   547	                                ->label('سبب الرفض')
   548	                                ->required()
   549	                                ->rows(3),
   550	                        ])
   551	                        ->action(function (Violation $record, array $data) {
   552	                            app(\App\Services\Disciplinary\ViolationService::class)
   553	                                ->dismiss($record, $data);
   554	                        })
   555	                        ->visible(fn (Violation $record) => !in_array($record->status, [
   556	                            ViolationStatus::DISMISSED,
   557	                            ViolationStatus::RESOLVED,
   558	                            ViolationStatus::APPEALED_OVERTURNED,
   559	                            ViolationStatus::PENALTY_IMPOSED,
   560	                        ])),
   561	                ]),
   562	            ])
   563	            ->bulkActions([
   564	                Tables\Actions\BulkActionGroup::make([
   565	                    Tables\Actions\DeleteBulkAction::make(),
   566	                ]),
   567	            ]);
   568	    }
   569	
   570	    public static function getRelations(): array
   571	    {
   572	        return [
   573	            RelationManagers\PenaltiesRelationManager::class,
   574	            RelationManagers\DocumentsRelationManager::class,
   575	        ];
   576	    }
   577	
   578	    public static function getPages(): array
   579	    {
   580	        return [
   581	            'index'  => Pages\ListViolations::route('/'),
   582	            'create' => Pages\CreateViolation::route('/create'),
   583	            'view'   => Pages\ViewViolation::route('/{record}'),
   584	            'edit'   => Pages\EditViolation::route('/{record}/edit'),
   585	        ];
   586	    }
   587	
   588	    public static function getEloquentQuery(): Builder
   589	    {
   590	        return parent::getEloquentQuery()
   591	            ->with(['member', 'violationType', 'reporter']);
   592	    }
   593	
   594	    public static function getGloballySearchableAttributes(): array
   595	    {
   596	        return ['violation_number', 'title', 'member.full_name_ar', 'member.membership_number'];
   597	    }
   598	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [190]: app/Filament/Resources/ViolationResource.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [191/494]: app/Filament/Resources/ViolationResource/Pages/CreateViolation.php
│ LANGUAGE: php | LINES: 23 | SIZE: 661 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	declare(strict_types=1);
     4	
     5	namespace App\Filament\Resources\ViolationResource\Pages;
     6	
     7	use App\Filament\Resources\ViolationResource;
     8	use App\Services\Disciplinary\ViolationService;
     9	use Filament\Resources\Pages\CreateRecord;
    10	
    11	class CreateViolation extends CreateRecord
    12	{
    13	    protected static string $resource = ViolationResource::class;
    14	
    15	    protected function handleRecordCreation(array $data): \Illuminate\Database\Eloquent\Model
    16	    {
    17	        return app(ViolationService::class)->reportViolation($data);
    18	    }
    19	
    20	    protected function getRedirectUrl(): string
    21	    {
    22	        return $this->getResource()::getUrl('view', ['record' => $this->record]);
    23	    }
    24	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [191]: app/Filament/Resources/ViolationResource/Pages/CreateViolation.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [192/494]: app/Filament/Resources/ViolationResource/Pages/EditViolation.php
│ LANGUAGE: php | LINES: 26 | SIZE: 625 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	declare(strict_types=1);
     4	
     5	namespace App\Filament\Resources\ViolationResource\Pages;
     6	
     7	use App\Filament\Resources\ViolationResource;
     8	use Filament\Actions;
     9	use Filament\Resources\Pages\EditRecord;
    10	
    11	class EditViolation extends EditRecord
    12	{
    13	    protected static string $resource = ViolationResource::class;
    14	
    15	    protected function getHeaderActions(): array
    16	    {
    17	        return [
    18	            Actions\ViewAction::make(),
    19	            Actions\DeleteAction::make(),
    20	        ];
    21	    }
    22	
    23	    protected function getRedirectUrl(): string
    24	    {
    25	        return $this->getResource()::getUrl('view', ['record' => $this->record]);
    26	    }
    27	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [192]: app/Filament/Resources/ViolationResource/Pages/EditViolation.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [193/494]: app/Filament/Resources/ViolationResource/Pages/ListViolations.php
│ LANGUAGE: php | LINES: 67 | SIZE: 2781 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	declare(strict_types=1);
     4	
     5	namespace App\Filament\Resources\ViolationResource\Pages;
     6	
     7	use App\Enums\ViolationSeverity;
     8	use App\Enums\ViolationStatus;
     9	use App\Filament\Resources\ViolationResource;
    10	use Filament\Actions;
    11	use Filament\Resources\Components\Tab;
    12	use Filament\Resources\Pages\ListRecords;
    13	use Illuminate\Database\Eloquent\Builder;
    14	
    15	class ListViolations extends ListRecords
    16	{
    17	    protected static string $resource = ViolationResource::class;
    18	
    19	    protected function getHeaderActions(): array
    20	    {
    21	        return [
    22	            Actions\CreateAction::make()
    23	                ->label('تسجيل مخالفة جديدة'),
    24	        ];
    25	    }
    26	
    27	    public function getTabs(): array
    28	    {
    29	        return [
    30	            'all' => Tab::make('الكل')
    31	                ->icon('heroicon-o-list-bullet'),
    32	
    33	            'reported' => Tab::make('جديدة')
    34	                ->icon('heroicon-o-bell-alert')
    35	                ->modifyQueryUsing(fn (Builder $query) => $query->where('status', ViolationStatus::REPORTED))
    36	                ->badge(fn () => static::getModel()::where('status', ViolationStatus::REPORTED)->count() ?: null)
    37	                ->badgeColor('danger'),
    38	
    39	            'investigating' => Tab::make('قيد التحقيق')
    40	                ->icon('heroicon-o-magnifying-glass')
    41	                ->modifyQueryUsing(fn (Builder $query) => $query->where('status', ViolationStatus::UNDER_INVESTIGATION))
    42	                ->badge(fn () => static::getModel()::where('status', ViolationStatus::UNDER_INVESTIGATION)->count() ?: null)
    43	                ->badgeColor('warning'),
    44	
    45	            'pending_board' => Tab::make('بانتظار المجلس')
    46	                ->icon('heroicon-o-building-library')
    47	                ->modifyQueryUsing(fn (Builder $query) => $query->where('status', ViolationStatus::PENDING_BOARD_REVIEW))
    48	                ->badge(fn () => static::getModel()::where('status', ViolationStatus::PENDING_BOARD_REVIEW)->count() ?: null)
    49	                ->badgeColor('info'),
    50	
    51	            'penalized' => Tab::make('تمت المعاقبة')
    52	                ->icon('heroicon-o-scale')
    53	                ->modifyQueryUsing(fn (Builder $query) => $query->where('status', ViolationStatus::PENALTY_IMPOSED)),
    54	
    55	            'critical' => Tab::make('خطيرة')
    56	                ->icon('heroicon-o-fire')
    57	                ->modifyQueryUsing(fn (Builder $query) => $query->whereIn('severity', [
    58	                    ViolationSeverity::MAJOR,
    59	                    ViolationSeverity::CRITICAL,
    60	                ]))
    61	                ->badgeColor('danger'),
    62	
    63	            'dismissed' => Tab::make('مرفوضة')
    64	                ->icon('heroicon-o-x-circle')
    65	                ->modifyQueryUsing(fn (Builder $query) => $query->where('status', ViolationStatus::DISMISSED)),
    66	        ];
    67	    }
    68	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [193]: app/Filament/Resources/ViolationResource/Pages/ListViolations.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [194/494]: app/Filament/Resources/ViolationResource/Pages/ViewViolation.php
│ LANGUAGE: php | LINES: 197 | SIZE: 8582 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	declare(strict_types=1);
     4	
     5	namespace App\Filament\Resources\ViolationResource\Pages;
     6	
     7	use App\Enums\PenaltyStatus;
     8	use App\Enums\ViolationSeverity;
     9	use App\Enums\ViolationStatus;
    10	use App\Filament\Resources\ViolationResource;
    11	use App\Models\PenaltyType;
    12	use App\Services\Disciplinary\ViolationService;
    13	use Filament\Actions;
    14	use Filament\Forms;
    15	use Filament\Resources\Pages\ViewRecord;
    16	
    17	class ViewViolation extends ViewRecord
    18	{
    19	    protected static string $resource = ViolationResource::class;
    20	
    21	    protected function getHeaderActions(): array
    22	    {
    23	        return [
    24	            Actions\EditAction::make(),
    25	
    26	            Actions\Action::make('start_investigation')
    27	                ->label('بدء التحقيق')
    28	                ->icon('heroicon-o-magnifying-glass')
    29	                ->color('info')
    30	                ->requiresConfirmation()
    31	                ->form([
    32	                    Forms\Components\Textarea::make('investigation_notes')
    33	                        ->label('ملاحظات أولية')
    34	                        ->rows(3),
    35	                ])
    36	                ->action(function (array $data) {
    37	                    app(ViolationService::class)->startInvestigation($this->record, $data);
    38	                    $this->refreshFormData(['status']);
    39	                })
    40	                ->visible(fn () => $this->record->status === ViolationStatus::REPORTED),
    41	
    42	            Actions\Action::make('complete_investigation')
    43	                ->label('إنهاء التحقيق')
    44	                ->icon('heroicon-o-check-circle')
    45	                ->color('success')
    46	                ->form([
    47	                    Forms\Components\Textarea::make('investigation_findings')
    48	                        ->label('نتائج التحقيق')
    49	                        ->required()
    50	                        ->rows(4),
    51	
    52	                    Forms\Components\Textarea::make('investigation_notes')
    53	                        ->label('ملاحظات')
    54	                        ->rows(3),
    55	
    56	                    Forms\Components\Select::make('severity')
    57	                        ->label('إعادة تقييم الخطورة')
    58	                        ->options(ViolationSeverity::toFilamentOptions()),
    59	                ])
    60	                ->action(function (array $data) {
    61	                    app(ViolationService::class)->completeInvestigation($this->record, $data);
    62	                    $this->refreshFormData(['status']);
    63	                })
    64	                ->visible(fn () => $this->record->status === ViolationStatus::UNDER_INVESTIGATION),
    65	
    66	            Actions\Action::make('escalate_to_board')
    67	                ->label('عرض على المجلس')
    68	                ->icon('heroicon-o-building-library')
    69	                ->color('warning')
    70	                ->requiresConfirmation()
    71	                ->form([
    72	                    Forms\Components\Textarea::make('escalation_reason')
    73	                        ->label('سبب العرض')
    74	                        ->rows(3),
    75	                ])
    76	                ->action(function (array $data) {
    77	                    app(ViolationService::class)->escalateToBoard($this->record, $data);
    78	                    $this->refreshFormData(['status']);
    79	                })
    80	                ->visible(fn () => in_array($this->record->status, [
    81	                    ViolationStatus::INVESTIGATION_COMPLETE,
    82	                    ViolationStatus::REPORTED,
    83	                ])),
    84	
    85	            Actions\Action::make('board_decision')
    86	                ->label('تسجيل قرار المجلس')
    87	                ->icon('heroicon-o-clipboard-document-check')
    88	                ->color('primary')
    89	                ->form([
    90	                    Forms\Components\Toggle::make('board_approved')
    91	                        ->label('موافقة المجلس')
    92	                        ->required(),
    93	
    94	                    Forms\Components\DatePicker::make('board_decision_date')
    95	                        ->label('تاريخ القرار')
    96	                        ->default(now()),
    97	
    98	                    Forms\Components\TextInput::make('board_decision_reference')
    99	                        ->label('رقم المرجع'),
   100	
   101	                    Forms\Components\Textarea::make('board_decision_notes')
   102	                        ->label('ملاحظات')
   103	                        ->rows(3),
   104	                ])
   105	                ->action(function (array $data) {
   106	                    app(ViolationService::class)->recordBoardDecision($this->record, $data);
   107	                    $this->refreshFormData(['status']);
   108	                })
   109	                ->visible(fn () => $this->record->status === ViolationStatus::PENDING_BOARD_REVIEW),
   110	
   111	            Actions\Action::make('impose_penalty')
   112	                ->label('فرض عقوبة')
   113	                ->icon('heroicon-o-scale')
   114	                ->color('danger')
   115	                ->form([
   116	                    Forms\Components\Select::make('penalty_type_id')
   117	                        ->label('نوع العقوبة')
   118	                        ->options(PenaltyType::where('is_active', true)->pluck('name_ar', 'id'))
   119	                        ->reactive()
   120	                        ->afterStateUpdated(function (Forms\Set $set, $state) {
   121	                            if ($state) {
   122	                                $type = PenaltyType::find($state);
   123	                                if ($type) {
   124	                                    $set('fine_amount', $type->default_fine_amount);
   125	                                    $set('requires_suspension', $type->requires_suspension);
   126	                                    $set('suspension_days', $type->default_suspension_days);
   127	                                    $set('description', $type->description);
   128	                                }
   129	                            }
   130	                        }),
   131	
   132	                    Forms\Components\Textarea::make('description')
   133	                        ->label('وصف العقوبة')
   134	                        ->required()
   135	                        ->rows(3),
   136	
   137	                    Forms\Components\TextInput::make('fine_amount')
   138	                        ->label('مبلغ الغرامة')
   139	                        ->numeric()
   140	                        ->prefix('ج.م')
   141	                        ->default(0),
   142	
   143	                    Forms\Components\DatePicker::make('effective_from')
   144	                        ->label('سارية من')
   145	                        ->default(now()),
   146	
   147	                    Forms\Components\DatePicker::make('effective_until')
   148	                        ->label('سارية حتى'),
   149	
   150	                    Forms\Components\Toggle::make('requires_suspension')
   151	                        ->label('يتطلب إيقاف')
   152	                        ->reactive(),
   153	
   154	                    Forms\Components\TextInput::make('suspension_days')
   155	                        ->label('أيام الإيقاف')
   156	                        ->numeric()
   157	                        ->visible(fn (Forms\Get $get) => $get('requires_suspension')),
   158	
   159	                    Forms\Components\TextInput::make('board_decision_reference')
   160	                        ->label('رقم قرار المجلس'),
   161	
   162	                    Forms\Components\Textarea::make('notes')
   163	                        ->label('ملاحظات')
   164	                        ->rows(2),
   165	                ])
   166	                ->action(function (array $data) {
   167	                    app(ViolationService::class)->imposePenalty($this->record, $data);
   168	                    $this->refreshFormData(['status']);
   169	                })
   170	                ->visible(fn () => in_array($this->record->status, [
   171	                    ViolationStatus::INVESTIGATION_COMPLETE,
   172	                    ViolationStatus::BOARD_REVIEWED,
   173	                ])),
   174	
   175	            Actions\Action::make('dismiss')
   176	                ->label('رفض المخالفة')
   177	                ->icon('heroicon-o-x-circle')
   178	                ->color('gray')
   179	                ->requiresConfirmation()
   180	                ->form([
   181	                    Forms\Components\Textarea::make('dismissal_reason')
   182	                        ->label('سبب الرفض')
   183	                        ->required()
   184	                        ->rows(3),
   185	                ])
   186	                ->action(function (array $data) {
   187	                    app(ViolationService::class)->dismiss($this->record, $data);
   188	                    $this->refreshFormData(['status']);
   189	                })
   190	                ->visible(fn () => !in_array($this->record->status, [
   191	                    ViolationStatus::DISMISSED,
   192	                    ViolationStatus::RESOLVED,
   193	                    ViolationStatus::APPEALED_OVERTURNED,
   194	                    ViolationStatus::PENALTY_IMPOSED,
   195	                ])),
   196	        ];
   197	    }
   198	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [194]: app/Filament/Resources/ViolationResource/Pages/ViewViolation.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [195/494]: app/Filament/Resources/ViolationResource/RelationManagers/DocumentsRelationManager.php
│ LANGUAGE: php | LINES: 105 | SIZE: 3906 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	declare(strict_types=1);
     4	
     5	namespace App\Filament\Resources\ViolationResource\RelationManagers;
     6	
     7	use Filament\Forms;
     8	use Filament\Forms\Form;
     9	use Filament\Resources\RelationManagers\RelationManager;
    10	use Filament\Tables;
    11	use Filament\Tables\Table;
    12	
    13	class DocumentsRelationManager extends RelationManager
    14	{
    15	    protected static string $relationship = 'documents';
    16	
    17	    protected static ?string $title = 'المستندات والأدلة';
    18	
    19	    protected static ?string $modelLabel = 'مستند';
    20	
    21	    protected static ?string $pluralModelLabel = 'المستندات';
    22	
    23	    public function form(Form $form): Form
    24	    {
    25	        return $form->schema([
    26	            Forms\Components\TextInput::make('title')
    27	                ->label('عنوان المستند')
    28	                ->required()
    29	                ->maxLength(255)
    30	                ->columnSpanFull(),
    31	
    32	            Forms\Components\Select::make('document_type')
    33	                ->label('نوع المستند')
    34	                ->options([
    35	                    'evidence_photo'  => 'صورة دليل',
    36	                    'witness_report'  => 'تقرير شاهد',
    37	                    'cctv_footage'    => 'كاميرات مراقبة',
    38	                    'official_report' => 'تقرير رسمي',
    39	                    'member_statement' => 'إفادة العضو',
    40	                    'other'           => 'أخرى',
    41	                ])
    42	                ->required(),
    43	
    44	            Forms\Components\FileUpload::make('file_path')
    45	                ->label('الملف')
    46	                ->required()
    47	                ->directory('violations/documents')
    48	                ->maxSize(10240)
    49	                ->acceptedFileTypes([
    50	                    'image/*',
    51	                    'application/pdf',
    52	                    'video/mp4',
    53	                    'video/avi',
    54	                    'application/msword',
    55	                    'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
    56	                ])
    57	                ->columnSpanFull(),
    58	
    59	            Forms\Components\Textarea::make('description')
    60	                ->label('الوصف')
    61	                ->rows(3)
    62	                ->columnSpanFull(),
    63	        ]);
    64	    }
    65	
    66	    public function table(Table $table): Table
    67	    {
    68	        return $table
    69	            ->recordTitleAttribute('title')
    70	            ->columns([
    71	                Tables\Columns\TextColumn::make('title')
    72	                    ->label('العنوان')
    73	                    ->searchable(),
    74	
    75	                Tables\Columns\TextColumn::make('document_type')
    76	                    ->label('النوع')
    77	                    ->badge()
    78	                    ->formatStateUsing(fn (string $state): string => match ($state) {
    79	                        'evidence_photo'   => 'صورة دليل',
    80	                        'witness_report'   => 'تقرير شاهد',
    81	                        'cctv_footage'     => 'كاميرات مراقبة',
    82	                        'official_report'  => 'تقرير رسمي',
    83	                        'member_statement' => 'إفادة العضو',
    84	                        default            => 'أخرى',
    85	                    }),
    86	
    87	                Tables\Columns\TextColumn::make('created_at')
    88	                    ->label('تاريخ الرفع')
    89	                    ->dateTime('d/m/Y H:i')
    90	                    ->sortable(),
    91	            ])
    92	            ->headerActions([
    93	                Tables\Actions\CreateAction::make()
    94	                    ->label('رفع مستند'),
    95	            ])
    96	            ->actions([
    97	                Tables\Actions\Action::make('download')
    98	                    ->label('تحميل')
    99	                    ->icon('heroicon-o-arrow-down-tray')
   100	                    ->url(fn ($record) => \Storage::url($record->file_path))
   101	                    ->openUrlInNewTab(),
   102	                Tables\Actions\ViewAction::make(),
   103	                Tables\Actions\DeleteAction::make(),
   104	            ]);
   105	    }
   106	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [195]: app/Filament/Resources/ViolationResource/RelationManagers/DocumentsRelationManager.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [196/494]: app/Filament/Resources/ViolationResource/RelationManagers/PenaltiesRelationManager.php
│ LANGUAGE: php | LINES: 192 | SIZE: 7982 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	declare(strict_types=1);
     4	
     5	namespace App\Filament\Resources\ViolationResource\RelationManagers;
     6	
     7	use App\Enums\PenaltyStatus;
     8	use App\Models\Penalty;
     9	use App\Models\PenaltyType;
    10	use App\Services\Disciplinary\PenaltyService;
    11	use Filament\Forms;
    12	use Filament\Forms\Form;
    13	use Filament\Resources\RelationManagers\RelationManager;
    14	use Filament\Tables;
    15	use Filament\Tables\Table;
    16	
    17	class PenaltiesRelationManager extends RelationManager
    18	{
    19	    protected static string $relationship = 'penalties';
    20	
    21	    protected static ?string $title = 'العقوبات';
    22	
    23	    protected static ?string $modelLabel = 'عقوبة';
    24	
    25	    protected static ?string $pluralModelLabel = 'العقوبات';
    26	
    27	    public function form(Form $form): Form
    28	    {
    29	        return $form->schema([
    30	            Forms\Components\Select::make('penalty_type_id')
    31	                ->label('نوع العقوبة')
    32	                ->options(PenaltyType::where('is_active', true)->pluck('name_ar', 'id'))
    33	                ->reactive()
    34	                ->afterStateUpdated(function (Forms\Set $set, $state) {
    35	                    if ($state) {
    36	                        $type = PenaltyType::find($state);
    37	                        if ($type) {
    38	                            $set('fine_amount', $type->default_fine_amount);
    39	                            $set('requires_suspension', $type->requires_suspension);
    40	                            $set('suspension_days', $type->default_suspension_days);
    41	                            $set('description', $type->description);
    42	                        }
    43	                    }
    44	                }),
    45	
    46	            Forms\Components\Textarea::make('description')
    47	                ->label('وصف العقوبة')
    48	                ->required()
    49	                ->rows(3)
    50	                ->columnSpanFull(),
    51	
    52	            Forms\Components\TextInput::make('fine_amount')
    53	                ->label('مبلغ الغرامة')
    54	                ->numeric()
    55	                ->prefix('ج.م')
    56	                ->default(0),
    57	
    58	            Forms\Components\DatePicker::make('effective_from')
    59	                ->label('سارية من')
    60	                ->default(now()),
    61	
    62	            Forms\Components\DatePicker::make('effective_until')
    63	                ->label('سارية حتى'),
    64	
    65	            Forms\Components\Toggle::make('requires_suspension')
    66	                ->label('يتطلب إيقاف')
    67	                ->reactive(),
    68	
    69	            Forms\Components\TextInput::make('suspension_days')
    70	                ->label('أيام الإيقاف')
    71	                ->numeric()
    72	                ->visible(fn (Forms\Get $get) => $get('requires_suspension')),
    73	
    74	            Forms\Components\TextInput::make('board_decision_reference')
    75	                ->label('رقم قرار المجلس'),
    76	
    77	            Forms\Components\Textarea::make('notes')
    78	                ->label('ملاحظات')
    79	                ->rows(2)
    80	                ->columnSpanFull(),
    81	        ]);
    82	    }
    83	
    84	    public function table(Table $table): Table
    85	    {
    86	        return $table
    87	            ->recordTitleAttribute('penalty_number')
    88	            ->columns([
    89	                Tables\Columns\TextColumn::make('penalty_number')
    90	                    ->label('رقم العقوبة')
    91	                    ->searchable()
    92	                    ->sortable()
    93	                    ->weight('bold'),
    94	
    95	                Tables\Columns\TextColumn::make('penaltyType.name_ar')
    96	                    ->label('النوع')
    97	                    ->default('—'),
    98	
    99	                Tables\Columns\TextColumn::make('status')
   100	                    ->label('الحالة')
   101	                    ->badge()
   102	                    ->formatStateUsing(fn ($state) => $state instanceof PenaltyStatus ? $state->getLabel() : $state)
   103	                    ->color(fn ($state) => $state instanceof PenaltyStatus ? $state->getColor() : 'gray'),
   104	
   105	                Tables\Columns\TextColumn::make('fine_amount')
   106	                    ->label('الغرامة')
   107	                    ->money('EGP')
   108	                    ->sortable(),
   109	
   110	                Tables\Columns\IconColumn::make('fine_paid')
   111	                    ->label('مسددة')
   112	                    ->boolean(),
   113	
   114	                Tables\Columns\IconColumn::make('requires_suspension')
   115	                    ->label('إيقاف')
   116	                    ->boolean(),
   117	
   118	                Tables\Columns\TextColumn::make('effective_from')
   119	                    ->label('من')
   120	                    ->date('d/m/Y')
   121	                    ->sortable(),
   122	
   123	                Tables\Columns\TextColumn::make('effective_until')
   124	                    ->label('حتى')
   125	                    ->date('d/m/Y')
   126	                    ->default('—'),
   127	
   128	                Tables\Columns\TextColumn::make('imposedBy.name')
   129	                    ->label('بواسطة'),
   130	            ])
   131	            ->filters([
   132	                Tables\Filters\SelectFilter::make('status')
   133	                    ->label('الحالة')
   134	                    ->options(PenaltyStatus::toFilamentOptions()),
   135	            ])
   136	            ->headerActions([
   137	                Tables\Actions\CreateAction::make()
   138	                    ->mutateFormDataUsing(function (array $data): array {
   139	                        $data['member_id'] = $this->getOwnerRecord()->member_id;
   140	                        return $data;
   141	                    })
   142	                    ->using(function (array $data) {
   143	                        $data['violation_id'] = $this->getOwnerRecord()->id;
   144	                        return app(PenaltyService::class)->createPenalty($data);
   145	                    }),
   146	            ])
   147	            ->actions([
   148	                Tables\Actions\ActionGroup::make([
   149	                    Tables\Actions\Action::make('record_payment')
   150	                        ->label('تسجيل سداد الغرامة')
   151	                        ->icon('heroicon-o-banknotes')
   152	                        ->color('success')
   153	                        ->requiresConfirmation()
   154	                        ->action(fn (Penalty $record) => app(PenaltyService::class)->recordFinePayment($record))
   155	                        ->visible(fn (Penalty $record) => $record->fine_amount > 0 && !$record->fine_paid),
   156	
   157	                    Tables\Actions\Action::make('waive')
   158	                        ->label('إعفاء')
   159	                        ->icon('heroicon-o-hand-raised')
   160	                        ->color('warning')
   161	                        ->requiresConfirmation()
   162	                        ->form([
   163	                            Forms\Components\Textarea::make('reason')
   164	                                ->label('سبب الإعفاء')
   165	                                ->required()
   166	                                ->rows(3),
   167	
   168	                            Forms\Components\TextInput::make('board_decision_reference')
   169	                                ->label('رقم قرار المجلس'),
   170	                        ])
   171	                        ->action(fn (Penalty $record, array $data) => app(PenaltyService::class)->waive($record, $data))
   172	                        ->visible(fn (Penalty $record) => in_array($record->status, [PenaltyStatus::ACTIVE, PenaltyStatus::ENFORCED])),
   173	
   174	                    Tables\Actions\Action::make('overturn')
   175	                        ->label('إلغاء (استئناف)')
   176	                        ->icon('heroicon-o-arrow-uturn-left')
   177	                        ->color('danger')
   178	                        ->requiresConfirmation()
   179	                        ->form([
   180	                            Forms\Components\Textarea::make('reason')
   181	                                ->label('سبب الإلغاء')
   182	                                ->required()
   183	                                ->rows(3),
   184	                        ])
   185	                        ->action(fn (Penalty $record, array $data) => app(PenaltyService::class)->overturn($record, $data))
   186	                        ->visible(fn (Penalty $record) => in_array($record->status, [PenaltyStatus::ACTIVE, PenaltyStatus::ENFORCED])),
   187	
   188	                    Tables\Actions\ViewAction::make(),
   189	                ]),
   190	            ])
   191	            ->bulkActions([]);
   192	    }
   193	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [196]: app/Filament/Resources/ViolationResource/RelationManagers/PenaltiesRelationManager.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [197/494]: app/Filament/Resources/ViolationTypeResource.php
│ LANGUAGE: php | LINES: 151 | SIZE: 5448 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	declare(strict_types=1);
     4	
     5	namespace App\Filament\Resources;
     6	
     7	use App\Enums\ViolationSeverity;
     8	use App\Filament\Resources\ViolationTypeResource\Pages;
     9	use App\Models\ViolationType;
    10	use Filament\Forms;
    11	use Filament\Forms\Form;
    12	use Filament\Resources\Resource;
    13	use Filament\Tables;
    14	use Filament\Tables\Table;
    15	
    16	class ViolationTypeResource extends Resource
    17	{
    18	    protected static ?string $model = ViolationType::class;
    19	
    20	    protected static ?string $navigationIcon = 'heroicon-o-clipboard-document-list';
    21	
    22	    protected static ?string $navigationGroup = 'الانضباط';
    23	
    24	    protected static ?string $navigationLabel = 'أنواع المخالفات';
    25	
    26	    protected static ?string $modelLabel = 'نوع مخالفة';
    27	
    28	    protected static ?string $pluralModelLabel = 'أنواع المخالفات';
    29	
    30	    protected static ?int $navigationSort = 4;
    31	
    32	    public static function form(Form $form): Form
    33	    {
    34	        return $form->schema([
    35	            Forms\Components\Section::make('بيانات نوع المخالفة')
    36	                ->schema([
    37	                    Forms\Components\TextInput::make('name_ar')
    38	                        ->label('الاسم بالعربية')
    39	                        ->required()
    40	                        ->maxLength(255),
    41	
    42	                    Forms\Components\TextInput::make('name_en')
    43	                        ->label('الاسم بالإنجليزية')
    44	                        ->maxLength(255),
    45	
    46	                    Forms\Components\TextInput::make('code')
    47	                        ->label('الكود')
    48	                        ->required()
    49	                        ->unique(ignoreRecord: true)
    50	                        ->maxLength(50),
    51	
    52	                    Forms\Components\Select::make('default_severity')
    53	                        ->label('درجة الخطورة الافتراضية')
    54	                        ->options(ViolationSeverity::toFilamentOptions())
    55	                        ->required(),
    56	
    57	                    Forms\Components\Textarea::make('description')
    58	                        ->label('الوصف')
    59	                        ->rows(3)
    60	                        ->columnSpanFull(),
    61	
    62	                    Forms\Components\Toggle::make('requires_investigation')
    63	                        ->label('يتطلب تحقيق')
    64	                        ->default(false),
    65	
    66	                    Forms\Components\Toggle::make('requires_board_review')
    67	                        ->label('يتطلب عرض على المجلس')
    68	                        ->default(false),
    69	
    70	                    Forms\Components\Toggle::make('is_active')
    71	                        ->label('نشط')
    72	                        ->default(true),
    73	
    74	                    Forms\Components\TextInput::make('display_order')
    75	                        ->label('ترتيب العرض')
    76	                        ->numeric()
    77	                        ->default(0),
    78	                ])
    79	                ->columns(2),
    80	        ]);
    81	    }
    82	
    83	    public static function table(Table $table): Table
    84	    {
    85	        return $table
    86	            ->columns([
    87	                Tables\Columns\TextColumn::make('code')
    88	                    ->label('الكود')
    89	                    ->searchable()
    90	                    ->sortable(),
    91	
    92	                Tables\Columns\TextColumn::make('name_ar')
    93	                    ->label('الاسم')
    94	                    ->searchable()
    95	                    ->sortable(),
    96	
    97	                Tables\Columns\TextColumn::make('default_severity')
    98	                    ->label('الخطورة الافتراضية')
    99	                    ->badge()
   100	                    ->formatStateUsing(fn ($state) => $state instanceof ViolationSeverity ? $state->getLabel() : $state)
   101	                    ->color(fn ($state) => $state instanceof ViolationSeverity ? $state->getColor() : 'gray'),
   102	
   103	                Tables\Columns\IconColumn::make('requires_investigation')
   104	                    ->label('يتطلب تحقيق')
   105	                    ->boolean(),
   106	
   107	                Tables\Columns\IconColumn::make('requires_board_review')
   108	                    ->label('يتطلب مجلس')
   109	                    ->boolean(),
   110	
   111	                Tables\Columns\IconColumn::make('is_active')
   112	                    ->label('نشط')
   113	                    ->boolean(),
   114	
   115	                Tables\Columns\TextColumn::make('violations_count')
   116	                    ->label('عدد المخالفات')
   117	                    ->counts('violations')
   118	                    ->sortable(),
   119	            ])
   120	            ->defaultSort('display_order')
   121	            ->filters([
   122	                Tables\Filters\SelectFilter::make('default_severity')
   123	                    ->label('الخطورة')
   124	                    ->options(ViolationSeverity::toFilamentOptions()),
   125	
   126	                Tables\Filters\TernaryFilter::make('is_active')
   127	                    ->label('نشط'),
   128	            ])
   129	            ->actions([
   130	                Tables\Actions\EditAction::make(),
   131	            ])
   132	            ->bulkActions([
   133	                Tables\Actions\BulkActionGroup::make([
   134	                    Tables\Actions\DeleteBulkAction::make(),
   135	                ]),
   136	            ]);
   137	    }
   138	
   139	    public static function getRelations(): array
   140	    {
   141	        return [];
   142	    }
   143	
   144	    public static function getPages(): array
   145	    {
   146	        return [
   147	            'index'  => Pages\ListViolationTypes::route('/'),
   148	            'create' => Pages\CreateViolationType::route('/create'),
   149	            'edit'   => Pages\EditViolationType::route('/{record}/edit'),
   150	        ];
   151	    }
   152	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [197]: app/Filament/Resources/ViolationTypeResource.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [198/494]: app/Filament/Resources/ViolationTypeResource/Pages/CreateViolationType.php
│ LANGUAGE: php | LINES: 17 | SIZE: 425 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	declare(strict_types=1);
     4	
     5	namespace App\Filament\Resources\ViolationTypeResource\Pages;
     6	
     7	use App\Filament\Resources\ViolationTypeResource;
     8	use Filament\Resources\Pages\CreateRecord;
     9	
    10	class CreateViolationType extends CreateRecord
    11	{
    12	    protected static string $resource = ViolationTypeResource::class;
    13	
    14	    protected function getRedirectUrl(): string
    15	    {
    16	        return $this->getResource()::getUrl('index');
    17	    }
    18	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [198]: app/Filament/Resources/ViolationTypeResource/Pages/CreateViolationType.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [199/494]: app/Filament/Resources/ViolationTypeResource/Pages/EditViolationType.php
│ LANGUAGE: php | LINES: 25 | SIZE: 573 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	declare(strict_types=1);
     4	
     5	namespace App\Filament\Resources\ViolationTypeResource\Pages;
     6	
     7	use App\Filament\Resources\ViolationTypeResource;
     8	use Filament\Actions;
     9	use Filament\Resources\Pages\EditRecord;
    10	
    11	class EditViolationType extends EditRecord
    12	{
    13	    protected static string $resource = ViolationTypeResource::class;
    14	
    15	    protected function getHeaderActions(): array
    16	    {
    17	        return [
    18	            Actions\DeleteAction::make(),
    19	        ];
    20	    }
    21	
    22	    protected function getRedirectUrl(): string
    23	    {
    24	        return $this->getResource()::getUrl('index');
    25	    }
    26	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [199]: app/Filament/Resources/ViolationTypeResource/Pages/EditViolationType.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [200/494]: app/Filament/Resources/ViolationTypeResource/Pages/ListViolationTypes.php
│ LANGUAGE: php | LINES: 20 | SIZE: 461 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	declare(strict_types=1);
     4	
     5	namespace App\Filament\Resources\ViolationTypeResource\Pages;
     6	
     7	use App\Filament\Resources\ViolationTypeResource;
     8	use Filament\Actions;
     9	use Filament\Resources\Pages\ListRecords;
    10	
    11	class ListViolationTypes extends ListRecords
    12	{
    13	    protected static string $resource = ViolationTypeResource::class;
    14	
    15	    protected function getHeaderActions(): array
    16	    {
    17	        return [
    18	            Actions\CreateAction::make(),
    19	        ];
    20	    }
    21	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [200]: app/Filament/Resources/ViolationTypeResource/Pages/ListViolationTypes.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [201/494]: app/Filament/Widgets/AdminStatsWidget.php
│ LANGUAGE: php | LINES: 53 | SIZE: 2108 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Filament\Widgets;
     4	
     5	use App\Models\User;
     6	use App\Models\BoardOffer;
     7	use App\Models\CashRegister;
     8	use App\Enums\BoardOfferStatus;
     9	use App\Enums\CashRegisterStatus;
    10	use Filament\Widgets\StatsOverviewWidget;
    11	use Filament\Widgets\StatsOverviewWidget\Stat;
    12	use Spatie\Activitylog\Models\Activity;
    13	
    14	class AdminStatsWidget extends StatsOverviewWidget
    15	{
    16	    protected static ?string $pollingInterval = '60s';
    17	
    18	    protected static ?int $sort = 1;
    19	
    20	    protected function getStats(): array
    21	    {
    22	        return [
    23	            Stat::make('المستخدمين النشطين', User::where('is_active', true)->count())
    24	                ->icon('heroicon-o-users')
    25	                ->color('success')
    26	                ->description('إجمالي المستخدمين: ' . User::count()),
    27	
    28	            Stat::make('عروض مجلس معلقة', BoardOffer::whereIn('status', [
    29	                BoardOfferStatus::PENDING,
    30	                BoardOfferStatus::UNDER_REVIEW,
    31	            ])->count())
    32	                ->icon('heroicon-o-clipboard-document-list')
    33	                ->color('warning')
    34	                ->description('إجمالي العروض: ' . BoardOffer::count()),
    35	
    36	            Stat::make('سجلات نقدية مفتوحة', CashRegister::where('status', CashRegisterStatus::OPEN)->count())
    37	                ->icon('heroicon-o-banknotes')
    38	                ->color('info')
    39	                ->description(function () {
    40	                    $total = CashRegister::where('status', CashRegisterStatus::OPEN)
    41	                        ->sum('current_balance');
    42	                    return 'الرصيد الإجمالي: ' . number_format($total, 2) . ' ج.م';
    43	                }),
    44	
    45	            Stat::make('نشاطات اليوم', Activity::whereDate('created_at', today())->count())
    46	                ->icon('heroicon-o-clipboard-document-check')
    47	                ->color('gray')
    48	                ->description(function () {
    49	                    $yesterday = Activity::whereDate('created_at', today()->subDay())->count();
    50	                    return "أمس: {$yesterday}";
    51	                }),
    52	        ];
    53	    }
    54	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [201]: app/Filament/Widgets/AdminStatsWidget.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [202/494]: app/Filament/Widgets/BoardOffersChartWidget.php
│ LANGUAGE: php | LINES: 63 | SIZE: 1680 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Filament\Widgets;
     4	
     5	use App\Models\BoardOffer;
     6	use App\Enums\BoardOfferStatus;
     7	use Filament\Widgets\ChartWidget;
     8	
     9	class BoardOffersChartWidget extends ChartWidget
    10	{
    11	    protected static ?string $heading = 'توزيع عروض المجلس';
    12	
    13	    protected static ?int $sort = 2;
    14	
    15	    protected static ?string $maxHeight = '300px';
    16	
    17	    protected function getType(): string
    18	    {
    19	        return 'doughnut';
    20	    }
    21	
    22	    protected function getData(): array
    23	    {
    24	        $statuses = [
    25	            BoardOfferStatus::PENDING,
    26	            BoardOfferStatus::UNDER_REVIEW,
    27	            BoardOfferStatus::APPROVED,
    28	            BoardOfferStatus::REJECTED,
    29	            BoardOfferStatus::DEFERRED,
    30	            BoardOfferStatus::CANCELLED,
    31	        ];
    32	
    33	        $data = [];
    34	        $labels = [];
    35	        $colors = [];
    36	
    37	        foreach ($statuses as $status) {
    38	            $count = BoardOffer::where('status', $status)->count();
    39	            if ($count > 0) {
    40	                $data[] = $count;
    41	                $labels[] = $status->getLabel();
    42	                $colors[] = match ($status->getColor()) {
    43	                    'warning' => '#F59E0B',
    44	                    'info' => '#3B82F6',
    45	                    'success' => '#10B981',
    46	                    'danger' => '#EF4444',
    47	                    'gray' => '#6B7280',
    48	                    'primary' => '#8B5CF6',
    49	                    default => '#6B7280',
    50	                };
    51	            }
    52	        }
    53	
    54	        return [
    55	            'datasets' => [
    56	                [
    57	                    'data' => $data,
    58	                    'backgroundColor' => $colors,
    59	                ],
    60	            ],
    61	            'labels' => $labels,
    62	        ];
    63	    }
    64	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [202]: app/Filament/Widgets/BoardOffersChartWidget.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [203/494]: app/Filament/Widgets/CardStatsWidget.php
│ LANGUAGE: php | LINES: 56 | SIZE: 2720 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Filament\Widgets;
     4	
     5	use App\Enums\CardStatus;
     6	use App\Models\MemberCard;
     7	use Filament\Widgets\StatsOverviewWidget;
     8	use Filament\Widgets\StatsOverviewWidget\Stat;
     9	
    10	class CardStatsWidget extends StatsOverviewWidget
    11	{
    12	    protected static ?string $pollingInterval = '60s';
    13	
    14	    protected function getStats(): array
    15	    {
    16	        $baseQuery = MemberCard::where('is_archived', false);
    17	
    18	        $totalActive = (clone $baseQuery)->where('status', CardStatus::ACTIVE->value)->count();
    19	        $totalPrinted = (clone $baseQuery)->where('status', CardStatus::PRINTED->value)->count();
    20	        $totalCollected = (clone $baseQuery)->where('status', CardStatus::COLLECTED->value)->count();
    21	        $pendingPrint = (clone $baseQuery)->where('status', CardStatus::ACTIVE->value)->whereNull('printed_at')->count();
    22	        $pendingCollection = (clone $baseQuery)->whereNotNull('printed_at')->whereNull('collected_at')
    23	            ->whereIn('status', [CardStatus::ACTIVE->value, CardStatus::PRINTED->value])->count();
    24	        $expired = (clone $baseQuery)
    25	            ->whereIn('status', [CardStatus::ACTIVE->value, CardStatus::PRINTED->value, CardStatus::COLLECTED->value])
    26	            ->where('expiry_date', '<', now()->toDateString())->count();
    27	        $suspended = (clone $baseQuery)->where('status', CardStatus::SUSPENDED->value)->count();
    28	
    29	        return [
    30	            Stat::make('كروت نشطة', $totalActive)
    31	                ->description('إجمالي الكروت الفعالة')
    32	                ->descriptionIcon('heroicon-o-identification')
    33	                ->color('success')
    34	                ->chart([$totalActive, $totalPrinted, $totalCollected]),
    35	
    36	            Stat::make('في انتظار الطباعة', $pendingPrint)
    37	                ->description('كروت جاهزة للطباعة')
    38	                ->descriptionIcon('heroicon-o-printer')
    39	                ->color($pendingPrint > 0 ? 'warning' : 'success'),
    40	
    41	            Stat::make('في انتظار الاستلام', $pendingCollection)
    42	                ->description('كروت مطبوعة لم تُستلم')
    43	                ->descriptionIcon('heroicon-o-hand-raised')
    44	                ->color($pendingCollection > 0 ? 'info' : 'success'),
    45	
    46	            Stat::make('منتهية الصلاحية', $expired)
    47	                ->description('تحتاج تجديد')
    48	                ->descriptionIcon('heroicon-o-exclamation-triangle')
    49	                ->color($expired > 0 ? 'danger' : 'success'),
    50	
    51	            Stat::make('معلقة', $suspended)
    52	                ->description('كروت موقوفة')
    53	                ->descriptionIcon('heroicon-o-pause-circle')
    54	                ->color($suspended > 0 ? 'warning' : 'gray'),
    55	        ];
    56	    }
    57	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [203]: app/Filament/Widgets/CardStatsWidget.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [204/494]: app/Filament/Widgets/CollectionByTypeWidget.php
│ LANGUAGE: php | LINES: 73 | SIZE: 1976 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Filament\Widgets;
     4	
     5	use App\Models\Receipt;
     6	use App\Enums\FeeCategory;
     7	use Filament\Widgets\ChartWidget;
     8	
     9	class CollectionByTypeWidget extends ChartWidget
    10	{
    11	    protected static ?string $heading = 'التحصيل حسب التصنيف - السنة الحالية';
    12	    protected static ?string $pollingInterval = '120s';
    13	    protected static ?int $sort = 4;
    14	
    15	    protected function getData(): array
    16	    {
    17	        $year = now()->year;
    18	
    19	        $categories = FeeCategory::cases();
    20	        $labels = [];
    21	        $amounts = [];
    22	        $colors = [
    23	            'rgba(59, 130, 246, 0.8)',
    24	            'rgba(16, 185, 129, 0.8)',
    25	            'rgba(245, 158, 11, 0.8)',
    26	            'rgba(239, 68, 68, 0.8)',
    27	            'rgba(139, 92, 246, 0.8)',
    28	            'rgba(236, 72, 153, 0.8)',
    29	            'rgba(14, 165, 233, 0.8)',
    30	            'rgba(168, 162, 158, 0.8)',
    31	            'rgba(34, 197, 94, 0.8)',
    32	            'rgba(251, 146, 60, 0.8)',
    33	        ];
    34	
    35	        foreach ($categories as $index => $category) {
    36	            $amount = Receipt::where('fee_category', $category->value)
    37	                ->where('fiscal_year', $year)
    38	                ->where('is_archived', false)
    39	                ->notVoided()
    40	                ->sum('amount_paid');
    41	
    42	            if ($amount > 0) {
    43	                $labels[] = $category->getLabel();
    44	                $amounts[] = (float) $amount;
    45	            }
    46	        }
    47	
    48	        return [
    49	            'datasets' => [
    50	                [
    51	                    'data' => $amounts,
    52	                    'backgroundColor' => array_slice($colors, 0, count($amounts)),
    53	                ],
    54	            ],
    55	            'labels' => $labels,
    56	        ];
    57	    }
    58	
    59	    protected function getType(): string
    60	    {
    61	        return 'doughnut';
    62	    }
    63	
    64	    protected function getOptions(): array
    65	    {
    66	        return [
    67	            'plugins' => [
    68	                'legend' => [
    69	                    'position' => 'bottom',
    70	                ],
    71	            ],
    72	        ];
    73	    }
    74	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [204]: app/Filament/Widgets/CollectionByTypeWidget.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [205/494]: app/Filament/Widgets/DailyReceiptsSummaryWidget.php
│ LANGUAGE: php | LINES: 53 | SIZE: 2300 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Filament\Widgets;
     4	
     5	use App\Models\Receipt;
     6	use Filament\Widgets\StatsOverviewWidget;
     7	use Filament\Widgets\StatsOverviewWidget\Stat;
     8	
     9	class DailyReceiptsSummaryWidget extends StatsOverviewWidget
    10	{
    11	    protected static ?string $pollingInterval = '30s';
    12	
    13	    protected function getStats(): array
    14	    {
    15	        $today = now()->toDateString();
    16	
    17	        $todayReceipts = Receipt::whereDate('receipt_date', $today)
    18	            ->where('is_archived', false);
    19	
    20	        $totalCollected = (clone $todayReceipts)->notVoided()->sum('amount_paid');
    21	        $receiptCount = (clone $todayReceipts)->notVoided()->count();
    22	        $voidedCount = (clone $todayReceipts)->where('status', 'voided')->count();
    23	        $voidedAmount = (clone $todayReceipts)->where('status', 'voided')->sum('total_amount');
    24	
    25	        // Week comparison
    26	        $lastWeekSameDay = now()->subWeek()->toDateString();
    27	        $lastWeekCollected = Receipt::whereDate('receipt_date', $lastWeekSameDay)
    28	            ->where('is_archived', false)
    29	            ->notVoided()
    30	            ->sum('amount_paid');
    31	
    32	        $changePercent = $lastWeekCollected > 0
    33	            ? round((($totalCollected - $lastWeekCollected) / $lastWeekCollected) * 100, 1)
    34	            : 0;
    35	
    36	        return [
    37	            Stat::make('إجمالي المحصل اليوم', number_format($totalCollected, 2) . ' جنيه')
    38	                ->description($changePercent >= 0 ? "↑ {$changePercent}% عن الأسبوع الماضي" : "↓ " . abs($changePercent) . "% عن الأسبوع الماضي")
    39	                ->descriptionIcon($changePercent >= 0 ? 'heroicon-m-arrow-trending-up' : 'heroicon-m-arrow-trending-down')
    40	                ->color($changePercent >= 0 ? 'success' : 'danger')
    41	                ->chart([7, 3, 4, 5, 6, 3, 5, 3]),
    42	
    43	            Stat::make('عدد الإيصالات', $receiptCount)
    44	                ->description('إيصال اليوم')
    45	                ->descriptionIcon('heroicon-m-document-text')
    46	                ->color('info'),
    47	
    48	            Stat::make('إيصالات ملغاة', $voidedCount)
    49	                ->description(number_format($voidedAmount, 2) . ' جنيه ملغي')
    50	                ->descriptionIcon('heroicon-m-x-circle')
    51	                ->color($voidedCount > 0 ? 'danger' : 'success'),
    52	        ];
    53	    }
    54	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [205]: app/Filament/Widgets/DailyReceiptsSummaryWidget.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [206/494]: app/Filament/Widgets/DependentRelationshipChartWidget.php
│ LANGUAGE: php | LINES: 82 | SIZE: 2358 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	declare(strict_types=1);
     4	
     5	namespace App\Filament\Widgets;
     6	
     7	use App\Enums\DependentStatus;
     8	use App\Models\Dependent;
     9	use Filament\Widgets\ChartWidget;
    10	
    11	final class DependentRelationshipChartWidget extends ChartWidget
    12	{
    13	    protected static ?string $heading = 'التابعون حسب صلة القرابة';
    14	
    15	    protected static ?string $pollingInterval = '60s';
    16	
    17	    protected static ?int $sort = 26;
    18	
    19	    protected static ?string $maxHeight = '300px';
    20	
    21	    protected function getData(): array
    22	    {
    23	        $data = Dependent::where('status', DependentStatus::ACTIVE)
    24	            ->selectRaw('relationship, COUNT(*) as count')
    25	            ->groupBy('relationship')
    26	            ->pluck('count', 'relationship')
    27	            ->toArray();
    28	
    29	        $labels = [];
    30	        $values = [];
    31	        $colors = [];
    32	
    33	        $relationshipMap = [
    34	            'spouse' => ['label' => 'زوج/زوجة', 'color' => '#3b82f6'],
    35	            'child' => ['label' => 'ابن/ابنة', 'color' => '#22c55e'],
    36	            'parent' => ['label' => 'أب/أم', 'color' => '#f59e0b'],
    37	            'sibling' => ['label' => 'أخ/أخت', 'color' => '#8b5cf6'],
    38	            'grandchild' => ['label' => 'حفيد/حفيدة', 'color' => '#ec4899'],
    39	            'other' => ['label' => 'أخرى', 'color' => '#6b7280'],
    40	        ];
    41	
    42	        foreach ($data as $relationship => $count) {
    43	            $info = $relationshipMap[$relationship] ?? ['label' => $relationship, 'color' => '#6b7280'];
    44	            $labels[] = $info['label'];
    45	            $values[] = $count;
    46	            $colors[] = $info['color'];
    47	        }
    48	
    49	        return [
    50	            'datasets' => [
    51	                [
    52	                    'data' => $values,
    53	                    'backgroundColor' => $colors,
    54	                    'borderWidth' => 0,
    55	                ],
    56	            ],
    57	            'labels' => $labels,
    58	        ];
    59	    }
    60	
    61	    protected function getType(): string
    62	    {
    63	        return 'doughnut';
    64	    }
    65	
    66	    protected function getOptions(): array
    67	    {
    68	        return [
    69	            'plugins' => [
    70	                'legend' => [
    71	                    'position' => 'bottom',
    72	                    'rtl' => true,
    73	                ],
    74	            ],
    75	            'cutout' => '60%',
    76	        ];
    77	    }
    78	
    79	    public static function canView(): bool
    80	    {
    81	        return auth()->user()?->can('view_any_dependent') ?? false;
    82	    }
    83	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [206]: app/Filament/Widgets/DependentRelationshipChartWidget.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [207/494]: app/Filament/Widgets/DependentStatsWidget.php
│ LANGUAGE: php | LINES: 83 | SIZE: 3229 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	declare(strict_types=1);
     4	
     5	namespace App\Filament\Widgets;
     6	
     7	use App\Enums\DependentStatus;
     8	use App\Models\Dependent;
     9	use Filament\Widgets\StatsOverviewWidget;
    10	use Filament\Widgets\StatsOverviewWidget\Stat;
    11	
    12	final class DependentStatsWidget extends StatsOverviewWidget
    13	{
    14	    protected static ?string $pollingInterval = '30s';
    15	
    16	    protected static ?int $sort = 25;
    17	
    18	    protected int|string|array $columnSpan = 'full';
    19	
    20	    protected function getStats(): array
    21	    {
    22	        $total = Dependent::where('is_archived', false)->count();
    23	        $active = Dependent::where('status', DependentStatus::ACTIVE)->count();
    24	        $pendingReview = Dependent::where('status', DependentStatus::PENDING_REVIEW)->count();
    25	        $pendingDocs = Dependent::where('status', DependentStatus::PENDING_DOCUMENTS)->count();
    26	        $suspended = Dependent::where('status', DependentStatus::SUSPENDED)->count();
    27	
    28	        $thisMonthNew = Dependent::where('created_at', '>=', now()->startOfMonth())->count();
    29	        $lastMonthNew = Dependent::whereBetween('created_at', [
    30	            now()->subMonth()->startOfMonth(),
    31	            now()->subMonth()->endOfMonth(),
    32	        ])->count();
    33	
    34	        $trend = $lastMonthNew > 0
    35	            ? round((($thisMonthNew - $lastMonthNew) / $lastMonthNew) * 100, 1)
    36	            : ($thisMonthNew > 0 ? 100 : 0);
    37	
    38	        $minors = Dependent::where('status', DependentStatus::ACTIVE)
    39	            ->where('date_of_birth', '>', now()->subYears(18))
    40	            ->count();
    41	
    42	        return [
    43	            Stat::make('إجمالي التابعين', number_format($total))
    44	                ->description("جديد هذا الشهر: {$thisMonthNew}")
    45	                ->descriptionIcon($trend >= 0 ? 'heroicon-m-arrow-trending-up' : 'heroicon-m-arrow-trending-down')
    46	                ->color($trend >= 0 ? 'success' : 'danger')
    47	                ->chart($this->getMonthlyChart())
    48	                ->icon('heroicon-o-user-group'),
    49	
    50	            Stat::make('نشط', number_format($active))
    51	                ->description(($total > 0 ? round(($active / $total) * 100, 1) : 0) . '% من الإجمالي')
    52	                ->color('success')
    53	                ->icon('heroicon-o-check-circle'),
    54	
    55	            Stat::make('بانتظار المراجعة', number_format($pendingReview + $pendingDocs))
    56	                ->description("مراجعة: {$pendingReview} | مستندات: {$pendingDocs}")
    57	                ->color($pendingReview > 0 ? 'warning' : 'gray')
    58	                ->icon('heroicon-o-clock'),
    59	
    60	            Stat::make('موقوف', number_format($suspended))
    61	                ->description("قُصّر نشطون: {$minors}")
    62	                ->color($suspended > 0 ? 'warning' : 'gray')
    63	                ->icon('heroicon-o-pause-circle'),
    64	        ];
    65	    }
    66	
    67	    private function getMonthlyChart(): array
    68	    {
    69	        $data = [];
    70	        for ($i = 5; $i >= 0; $i--) {
    71	            $data[] = Dependent::whereBetween('created_at', [
    72	                now()->subMonths($i)->startOfMonth(),
    73	                now()->subMonths($i)->endOfMonth(),
    74	            ])->count();
    75	        }
    76	
    77	        return $data;
    78	    }
    79	
    80	    public static function canView(): bool
    81	    {
    82	        return auth()->user()?->can('view_any_dependent') ?? false;
    83	    }
    84	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [207]: app/Filament/Widgets/DependentStatsWidget.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [208/494]: app/Filament/Widgets/DisciplinaryOverviewWidget.php
│ LANGUAGE: php | LINES: 81 | SIZE: 3032 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	declare(strict_types=1);
     4	
     5	namespace App\Filament\Widgets;
     6	
     7	use App\Enums\PenaltyStatus;
     8	use App\Enums\SuspensionStatus;
     9	use App\Enums\ViolationSeverity;
    10	use App\Enums\ViolationStatus;
    11	use App\Models\MemberSuspension;
    12	use App\Models\Penalty;
    13	use App\Models\Violation;
    14	use Filament\Widgets\StatsOverviewWidget;
    15	use Filament\Widgets\StatsOverviewWidget\Stat;
    16	
    17	class DisciplinaryOverviewWidget extends StatsOverviewWidget
    18	{
    19	    protected static ?string $pollingInterval = '30s';
    20	
    21	    protected static ?int $sort = 10;
    22	
    23	    protected function getStats(): array
    24	    {
    25	        $openViolations = Violation::whereNotIn('status', [
    26	            ViolationStatus::DISMISSED,
    27	            ViolationStatus::RESOLVED,
    28	            ViolationStatus::APPEALED_OVERTURNED,
    29	        ])->count();
    30	
    31	        $criticalViolations = Violation::whereIn('severity', [
    32	            ViolationSeverity::MAJOR,
    33	            ViolationSeverity::CRITICAL,
    34	        ])->whereNotIn('status', [
    35	            ViolationStatus::DISMISSED,
    36	            ViolationStatus::RESOLVED,
    37	            ViolationStatus::APPEALED_OVERTURNED,
    38	        ])->count();
    39	
    40	        $activePenalties = Penalty::whereIn('status', [
    41	            PenaltyStatus::ACTIVE,
    42	            PenaltyStatus::ENFORCED,
    43	        ])->count();
    44	
    45	        $unpaidFines = Penalty::whereIn('status', [PenaltyStatus::ACTIVE, PenaltyStatus::ENFORCED])
    46	            ->where('fine_amount', '>', 0)
    47	            ->where('fine_paid', false)
    48	            ->sum('fine_amount');
    49	
    50	        $activeSuspensions = MemberSuspension::where('status', SuspensionStatus::ACTIVE)->count();
    51	
    52	        $pendingBoard = Violation::where('status', ViolationStatus::PENDING_BOARD_REVIEW)->count();
    53	
    54	        return [
    55	            Stat::make('مخالفات مفتوحة', $openViolations)
    56	                ->description($criticalViolations > 0 ? "{$criticalViolations} خطيرة" : 'لا توجد خطيرة')
    57	                ->descriptionIcon($criticalViolations > 0 ? 'heroicon-o-fire' : 'heroicon-o-check-circle')
    58	                ->color($criticalViolations > 0 ? 'danger' : 'success')
    59	                ->chart($this->getViolationTrend()),
    60	
    61	            Stat::make('عقوبات نشطة', $activePenalties)
    62	                ->description('غرامات غير مسددة: ' . number_format($unpaidFines, 2) . ' ج.م')
    63	                ->descriptionIcon('heroicon-o-banknotes')
    64	                ->color($unpaidFines > 0 ? 'warning' : 'success'),
    65	
    66	            Stat::make('أعضاء موقوفون', $activeSuspensions)
    67	                ->description($pendingBoard > 0 ? "{$pendingBoard} بانتظار المجلس" : 'لا يوجد معلق')
    68	                ->descriptionIcon('heroicon-o-no-symbol')
    69	                ->color($activeSuspensions > 0 ? 'danger' : 'success'),
    70	        ];
    71	    }
    72	
    73	    protected function getViolationTrend(): array
    74	    {
    75	        $data = [];
    76	        for ($i = 6; $i >= 0; $i--) {
    77	            $date = now()->subDays($i);
    78	            $data[] = Violation::whereDate('created_at', $date)->count();
    79	        }
    80	        return $data;
    81	    }
    82	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [208]: app/Filament/Widgets/DisciplinaryOverviewWidget.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [209/494]: app/Filament/Widgets/DocumentComplianceWidget.php
│ LANGUAGE: php | LINES: 59 | SIZE: 2457 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Filament\Widgets;
     4	
     5	use App\Models\MemberDocument;
     6	use Filament\Widgets\StatsOverviewWidget;
     7	use Filament\Widgets\StatsOverviewWidget\Stat;
     8	
     9	class DocumentComplianceWidget extends StatsOverviewWidget
    10	{
    11	    protected static ?string $pollingInterval = '120s';
    12	
    13	    protected function getStats(): array
    14	    {
    15	        $baseQuery = MemberDocument::where('is_archived', false);
    16	
    17	        $totalDocuments = (clone $baseQuery)->count();
    18	        $verified = (clone $baseQuery)->where('is_verified', true)->count();
    19	        $pendingVerification = (clone $baseQuery)->where('is_verified', false)->count();
    20	        $expired = (clone $baseQuery)
    21	            ->whereNotNull('expiry_date')
    22	            ->where('expiry_date', '<', now()->toDateString())
    23	            ->count();
    24	        $expiringSoon = (clone $baseQuery)
    25	            ->whereNotNull('expiry_date')
    26	            ->whereBetween('expiry_date', [now()->toDateString(), now()->addDays(30)->toDateString()])
    27	            ->count();
    28	
    29	        return [
    30	            Stat::make('إجمالي المستندات', $totalDocuments)
    31	                ->description('جميع المستندات المرفوعة')
    32	                ->descriptionIcon('heroicon-o-document-duplicate')
    33	                ->color('primary'),
    34	
    35	            Stat::make('تم التحقق', $verified)
    36	                ->description(
    37	                    $totalDocuments > 0
    38	                        ? round(($verified / $totalDocuments) * 100, 1) . '% من الإجمالي'
    39	                        : 'لا توجد مستندات'
    40	                )
    41	                ->descriptionIcon('heroicon-o-check-badge')
    42	                ->color('success'),
    43	
    44	            Stat::make('في انتظار التحقق', $pendingVerification)
    45	                ->description('مستندات تحتاج مراجعة')
    46	                ->descriptionIcon('heroicon-o-clock')
    47	                ->color($pendingVerification > 0 ? 'warning' : 'success'),
    48	
    49	            Stat::make('منتهية الصلاحية', $expired)
    50	                ->description('تحتاج تحديث')
    51	                ->descriptionIcon('heroicon-o-exclamation-triangle')
    52	                ->color($expired > 0 ? 'danger' : 'success'),
    53	
    54	            Stat::make('تنتهي قريباً', $expiringSoon)
    55	                ->description('خلال 30 يوم')
    56	                ->descriptionIcon('heroicon-o-bell-alert')
    57	                ->color($expiringSoon > 0 ? 'warning' : 'gray'),
    58	        ];
    59	    }
    60	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [209]: app/Filament/Widgets/DocumentComplianceWidget.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [210/494]: app/Filament/Widgets/FinancialChartWidget.php
│ LANGUAGE: php | LINES: 69 | SIZE: 1904 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Filament\Widgets;
     4	
     5	use App\Models\Receipt;
     6	use Filament\Widgets\ChartWidget;
     7	use Illuminate\Support\Carbon;
     8	
     9	class FinancialChartWidget extends ChartWidget
    10	{
    11	    protected static ?string $heading = 'التحصيل المالي - آخر 30 يوم';
    12	    protected static ?string $pollingInterval = '120s';
    13	    protected static ?int $sort = 3;
    14	
    15	    protected function getData(): array
    16	    {
    17	        $days = collect();
    18	        $amounts = collect();
    19	
    20	        for ($i = 29; $i >= 0; $i--) {
    21	            $date = Carbon::now()->subDays($i);
    22	            $days->push($date->format('d/m'));
    23	
    24	            $dayTotal = Receipt::whereDate('receipt_date', $date->toDateString())
    25	                ->where('is_archived', false)
    26	                ->notVoided()
    27	                ->sum('amount_paid');
    28	
    29	            $amounts->push((float) $dayTotal);
    30	        }
    31	
    32	        return [
    33	            'datasets' => [
    34	                [
    35	                    'label' => 'المحصل (جنيه)',
    36	                    'data' => $amounts->toArray(),
    37	                    'backgroundColor' => 'rgba(16, 185, 129, 0.2)',
    38	                    'borderColor' => 'rgb(16, 185, 129)',
    39	                    'fill' => true,
    40	                    'tension' => 0.3,
    41	                ],
    42	            ],
    43	            'labels' => $days->toArray(),
    44	        ];
    45	    }
    46	
    47	    protected function getType(): string
    48	    {
    49	        return 'line';
    50	    }
    51	
    52	    protected function getOptions(): array
    53	    {
    54	        return [
    55	            'scales' => [
    56	                'y' => [
    57	                    'beginAtZero' => true,
    58	                    'ticks' => [
    59	                        'callback' => "function(value) { return value.toLocaleString() + ' جنيه'; }",
    60	                    ],
    61	                ],
    62	            ],
    63	            'plugins' => [
    64	                'legend' => [
    65	                    'display' => true,
    66	                ],
    67	            ],
    68	        ];
    69	    }
    70	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [210]: app/Filament/Widgets/FinancialChartWidget.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [211/494]: app/Filament/Widgets/MemberStatsOverview.php
│ LANGUAGE: php | LINES: 82 | SIZE: 3438 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Filament\Widgets;
     4	
     5	use App\Enums\MembershipStatus;
     6	use App\Models\Member;
     7	use Filament\Widgets\StatsOverviewWidget;
     8	use Filament\Widgets\StatsOverviewWidget\Stat;
     9	use Illuminate\Support\Facades\Cache;
    10	
    11	class MemberStatsOverview extends StatsOverviewWidget
    12	{
    13	    protected static ?string $pollingInterval = '60s';
    14	
    15	    protected static ?int $sort = 1;
    16	
    17	    protected int|string|array $columnSpan = 'full';
    18	
    19	    protected function getStats(): array
    20	    {
    21	        $stats = Cache::remember('member_stats_widget', 300, function () {
    22	            return [
    23	                'total' => Member::count(),
    24	                'active' => Member::where('membership_status', MembershipStatus::ACTIVE)->count(),
    25	                'pending' => Member::where('membership_status', MembershipStatus::PENDING)->count(),
    26	                'suspended' => Member::where('membership_status', MembershipStatus::SUSPENDED)->count(),
    27	                'new_this_month' => Member::whereMonth('created_at', now()->month)->whereYear('created_at', now()->year)->count(),
    28	                'overdue' => Member::where('workflow_stage', 7)
    29	                    ->where('membership_status', MembershipStatus::PENDING)
    30	                    ->whereNotNull('approval_date')
    31	                    ->where('approval_date', '<=', now()->subDays(15))
    32	                    ->count(),
    33	            ];
    34	        });
    35	
    36	        return [
    37	            Stat::make('إجمالي الأعضاء', number_format($stats['total']))
    38	                ->description('جميع الأعضاء في النظام')
    39	                ->icon('heroicon-o-user-group')
    40	                ->color('gray')
    41	                ->chart($this->getMonthlyTrend()),
    42	
    43	            Stat::make('أعضاء نشطون', number_format($stats['active']))
    44	                ->description(round(($stats['active'] / max($stats['total'], 1)) * 100, 1) . '% من الإجمالي')
    45	                ->icon('heroicon-o-check-circle')
    46	                ->color('success'),
    47	
    48	            Stat::make('قيد الإجراء', number_format($stats['pending']))
    49	                ->description('طلبات تحت المعالجة')
    50	                ->icon('heroicon-o-clock')
    51	                ->color('warning'),
    52	
    53	            Stat::make('موقوفون', number_format($stats['suspended']))
    54	                ->description('عضويات موقوفة')
    55	                ->icon('heroicon-o-pause-circle')
    56	                ->color('danger'),
    57	
    58	            Stat::make('جدد هذا الشهر', number_format($stats['new_this_month']))
    59	                ->description('طلبات جديدة هذا الشهر')
    60	                ->icon('heroicon-o-arrow-trending-up')
    61	                ->color('info'),
    62	
    63	            Stat::make('متأخر السداد', number_format($stats['overdue']))
    64	                ->description('تجاوزوا مهلة 15 يوم')
    65	                ->icon('heroicon-o-exclamation-triangle')
    66	                ->color($stats['overdue'] > 0 ? 'danger' : 'success'),
    67	        ];
    68	    }
    69	
    70	    private function getMonthlyTrend(): array
    71	    {
    72	        return Cache::remember('member_monthly_trend', 3600, function () {
    73	            $trend = [];
    74	            for ($i = 5; $i >= 0; $i--) {
    75	                $date = now()->subMonths($i);
    76	                $trend[] = Member::whereMonth('created_at', $date->month)
    77	                    ->whereYear('created_at', $date->year)
    78	                    ->count();
    79	            }
    80	            return $trend;
    81	        });
    82	    }
    83	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [211]: app/Filament/Widgets/MemberStatsOverview.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [212/494]: app/Filament/Widgets/MemberWorkflowFunnel.php
│ LANGUAGE: php | LINES: 87 | SIZE: 2436 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Filament\Widgets;
     4	
     5	use App\Enums\MembershipStatus;
     6	use App\Models\Member;
     7	use Filament\Widgets\ChartWidget;
     8	
     9	class MemberWorkflowFunnel extends ChartWidget
    10	{
    11	    protected static ?string $heading = 'قمع مراحل العضوية';
    12	
    13	    protected static ?string $pollingInterval = '120s';
    14	
    15	    protected static ?int $sort = 3;
    16	
    17	    protected int|string|array $columnSpan = 'full';
    18	
    19	    protected static ?string $maxHeight = '300px';
    20	
    21	    protected function getData(): array
    22	    {
    23	        $stageLabels = [
    24	            1 => 'الاستمارة',
    25	            2 => 'المستندات',
    26	            3 => 'رسوم الاستمارة',
    27	            4 => 'المراجعة',
    28	            5 => 'المقابلة',
    29	            6 => 'قرار المجلس',
    30	            7 => 'سداد العضوية',
    31	            8 => 'الكارنيهات',
    32	        ];
    33	
    34	        $counts = Member::where('membership_status', MembershipStatus::PENDING)
    35	            ->selectRaw('workflow_stage, COUNT(*) as count')
    36	            ->groupBy('workflow_stage')
    37	            ->pluck('count', 'workflow_stage')
    38	            ->toArray();
    39	
    40	        $data = [];
    41	        $labels = [];
    42	        foreach ($stageLabels as $num => $label) {
    43	            $labels[] = "م{$num}: {$label}";
    44	            $data[] = $counts[$num] ?? 0;
    45	        }
    46	
    47	        return [
    48	            'datasets' => [
    49	                [
    50	                    'label' => 'عدد الأعضاء',
    51	                    'data' => $data,
    52	                    'backgroundColor' => [
    53	                        '#94a3b8', // gray
    54	                        '#64748b',
    55	                        '#f59e0b', // amber
    56	                        '#eab308', // yellow
    57	                        '#3b82f6', // blue
    58	                        '#6366f1', // indigo
    59	                        '#22c55e', // green
    60	                        '#10b981', // emerald
    61	                    ],
    62	                    'borderWidth' => 0,
    63	                ],
    64	            ],
    65	            'labels' => $labels,
    66	        ];
    67	    }
    68	
    69	    protected function getType(): string
    70	    {
    71	        return 'bar';
    72	    }
    73	
    74	    protected function getOptions(): array
    75	    {
    76	        return [
    77	            'plugins' => [
    78	                'legend' => ['display' => false],
    79	            ],
    80	            'scales' => [
    81	                'y' => [
    82	                    'beginAtZero' => true,
    83	                    'ticks' => ['precision' => 0],
    84	                ],
    85	            ],
    86	        ];
    87	    }
    88	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [212]: app/Filament/Widgets/MemberWorkflowFunnel.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [213/494]: app/Filament/Widgets/MembershipGrowthChart.php
│ LANGUAGE: php | LINES: 95 | SIZE: 2836 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Filament\Widgets;
     4	
     5	use App\Services\Reporting\DashboardStatisticsService;
     6	use Filament\Widgets\ChartWidget;
     7	
     8	class MembershipGrowthChart extends ChartWidget
     9	{
    10	    protected static ?string $heading = 'نمو العضويات';
    11	
    12	    protected static ?int $sort = 2;
    13	
    14	    protected int | string | array $columnSpan = 'full';
    15	
    16	    protected static ?string $maxHeight = '300px';
    17	
    18	    protected static ?string $pollingInterval = '120s';
    19	
    20	    public ?string $filter = '12';
    21	
    22	    protected function getFilters(): ?array
    23	    {
    24	        return [
    25	            '6' => 'آخر 6 أشهر',
    26	            '12' => 'آخر 12 شهر',
    27	            '24' => 'آخر 24 شهر',
    28	        ];
    29	    }
    30	
    31	    protected function getData(): array
    32	    {
    33	        $months = (int) $this->filter;
    34	        $service = app(DashboardStatisticsService::class);
    35	        $data = $service->getMembershipGrowthData($months);
    36	
    37	        return [
    38	            'datasets' => [
    39	                [
    40	                    'label' => 'أعضاء جدد',
    41	                    'data' => array_map(fn ($item) => $item['new_members'], $data),
    42	                    'backgroundColor' => 'rgba(59, 130, 246, 0.5)',
    43	                    'borderColor' => 'rgb(59, 130, 246)',
    44	                    'borderWidth' => 2,
    45	                ],
    46	                [
    47	                    'label' => 'إجمالي النشطون',
    48	                    'data' => array_map(fn ($item) => $item['total_active'], $data),
    49	                    'backgroundColor' => 'rgba(16, 185, 129, 0.1)',
    50	                    'borderColor' => 'rgb(16, 185, 129)',
    51	                    'borderWidth' => 2,
    52	                    'type' => 'line',
    53	                    'yAxisID' => 'y1',
    54	                ],
    55	            ],
    56	            'labels' => array_map(fn ($item) => $item['month'], $data),
    57	        ];
    58	    }
    59	
    60	    protected function getType(): string
    61	    {
    62	        return 'bar';
    63	    }
    64	
    65	    protected function getOptions(): array
    66	    {
    67	        return [
    68	            'scales' => [
    69	                'y' => [
    70	                    'beginAtZero' => true,
    71	                    'position' => 'left',
    72	                    'title' => [
    73	                        'display' => true,
    74	                        'text' => 'أعضاء جدد',
    75	                    ],
    76	                ],
    77	                'y1' => [
    78	                    'beginAtZero' => true,
    79	                    'position' => 'right',
    80	                    'grid' => [
    81	                        'drawOnChartArea' => false,
    82	                    ],
    83	                    'title' => [
    84	                        'display' => true,
    85	                        'text' => 'إجمالي النشطون',
    86	                    ],
    87	                ],
    88	            ],
    89	            'plugins' => [
    90	                'legend' => [
    91	                    'position' => 'bottom',
    92	                ],
    93	            ],
    94	        ];
    95	    }
    96	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [213]: app/Filament/Widgets/MembershipGrowthChart.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [214/494]: app/Filament/Widgets/MembershipStatusChart.php
│ LANGUAGE: php | LINES: 69 | SIZE: 1957 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Filament\Widgets;
     4	
     5	use App\Services\Reporting\DashboardStatisticsService;
     6	use Filament\Widgets\ChartWidget;
     7	
     8	class MembershipStatusChart extends ChartWidget
     9	{
    10	    protected static ?string $heading = 'توزيع حالات العضوية';
    11	
    12	    protected static ?int $sort = 4;
    13	
    14	    protected int | string | array $columnSpan = 1;
    15	
    16	    protected static ?string $maxHeight = '280px';
    17	
    18	    protected static ?string $pollingInterval = '120s';
    19	
    20	    protected function getData(): array
    21	    {
    22	        $service = app(DashboardStatisticsService::class);
    23	        $data = $service->getMembershipStatusDistribution();
    24	
    25	        $colorMap = [
    26	            'success' => 'rgb(16, 185, 129)',
    27	            'danger' => 'rgb(239, 68, 68)',
    28	            'warning' => 'rgb(245, 158, 11)',
    29	            'info' => 'rgb(59, 130, 246)',
    30	            'primary' => 'rgb(99, 102, 241)',
    31	            'secondary' => 'rgb(107, 114, 128)',
    32	            'gray' => 'rgb(156, 163, 175)',
    33	        ];
    34	
    35	        return [
    36	            'datasets' => [
    37	                [
    38	                    'data' => array_map(fn ($item) => $item['count'], $data),
    39	                    'backgroundColor' => array_map(
    40	                        fn ($item) => $colorMap[$item['color']] ?? 'rgb(156, 163, 175)',
    41	                        $data
    42	                    ),
    43	                    'borderWidth' => 0,
    44	                ],
    45	            ],
    46	            'labels' => array_map(fn ($item) => $item['label'], $data),
    47	        ];
    48	    }
    49	
    50	    protected function getType(): string
    51	    {
    52	        return 'doughnut';
    53	    }
    54	
    55	    protected function getOptions(): array
    56	    {
    57	        return [
    58	            'plugins' => [
    59	                'legend' => [
    60	                    'position' => 'bottom',
    61	                    'labels' => [
    62	                        'padding' => 15,
    63	                        'usePointStyle' => true,
    64	                    ],
    65	                ],
    66	            ],
    67	            'cutout' => '60%',
    68	        ];
    69	    }
    70	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [214]: app/Filament/Widgets/MembershipStatusChart.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [215/494]: app/Filament/Widgets/OverdueInstallmentsWidget.php
│ LANGUAGE: php | LINES: 60 | SIZE: 2031 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Filament\Widgets;
     4	
     5	use App\Enums\InstallmentStatus;
     6	use App\Models\Installment;
     7	use Filament\Tables;
     8	use Filament\Tables\Table;
     9	use Filament\Widgets\TableWidget as BaseWidget;
    10	use Illuminate\Support\Carbon;
    11	
    12	class OverdueInstallmentsWidget extends BaseWidget
    13	{
    14	    protected static ?string $heading = 'أقساط متأخرة';
    15	
    16	    protected static ?int $sort = 9;
    17	
    18	    protected int | string | array $columnSpan = 1;
    19	
    20	    protected static ?string $pollingInterval = '120s';
    21	
    22	    public function table(Table $table): Table
    23	    {
    24	        return $table
    25	            ->query(
    26	                Installment::with(['installmentPlan.member'])
    27	                    ->where('status', InstallmentStatus::Pending)
    28	                    ->where('due_date', '<', Carbon::now())
    29	                    ->orderBy('due_date')
    30	                    ->limit(10)
    31	            )
    32	            ->columns([
    33	                Tables\Columns\TextColumn::make('installmentPlan.member.membership_number')
    34	                    ->label('رقم العضوية')
    35	                    ->badge()
    36	                    ->color('primary'),
    37	
    38	                Tables\Columns\TextColumn::make('installmentPlan.member.full_name_ar')
    39	                    ->label('العضو')
    40	                    ->limit(25),
    41	
    42	                Tables\Columns\TextColumn::make('amount')
    43	                    ->label('المبلغ')
    44	                    ->money('EGP')
    45	                    ->color('danger'),
    46	
    47	                Tables\Columns\TextColumn::make('due_date')
    48	                    ->label('تاريخ الاستحقاق')
    49	                    ->date('d/m/Y')
    50	                    ->color('danger'),
    51	
    52	                Tables\Columns\TextColumn::make('days_overdue')
    53	                    ->label('أيام التأخير')
    54	                    ->state(fn (Installment $record) => Carbon::now()->diffInDays($record->due_date))
    55	                    ->badge()
    56	                    ->color('danger')
    57	                    ->suffix(' يوم'),
    58	            ])
    59	            ->paginated(false);
    60	    }
    61	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [215]: app/Filament/Widgets/OverdueInstallmentsWidget.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [216/494]: app/Filament/Widgets/PendingActionsWidget.php
│ LANGUAGE: php | LINES: 69 | SIZE: 2433 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Filament\Widgets;
     4	
     5	use App\Models\WorkflowProgress;
     6	use Filament\Tables;
     7	use Filament\Tables\Table;
     8	use Filament\Widgets\TableWidget as BaseWidget;
     9	
    10	class PendingActionsWidget extends BaseWidget
    11	{
    12	    protected static ?string $heading = 'إجراءات في الانتظار';
    13	
    14	    protected static ?int $sort = 7;
    15	
    16	    protected int | string | array $columnSpan = 'full';
    17	
    18	    protected static ?string $pollingInterval = '60s';
    19	
    20	    public function table(Table $table): Table
    21	    {
    22	        return $table
    23	            ->query(
    24	                WorkflowProgress::with(['member'])
    25	                    ->where('is_current_stage', true)
    26	                    ->whereNull('completed_at')
    27	                    ->orderBy('created_at')
    28	                    ->limit(10)
    29	            )
    30	            ->columns([
    31	                Tables\Columns\TextColumn::make('member.membership_number')
    32	                    ->label('رقم العضوية')
    33	                    ->badge()
    34	                    ->color('primary'),
    35	
    36	                Tables\Columns\TextColumn::make('member.full_name_ar')
    37	                    ->label('العضو')
    38	                    ->searchable()
    39	                    ->limit(30),
    40	
    41	                Tables\Columns\TextColumn::make('stage_name_ar')
    42	                    ->label('المرحلة الحالية')
    43	                    ->badge()
    44	                    ->color('warning'),
    45	
    46	                Tables\Columns\TextColumn::make('status')
    47	                    ->label('الحالة')
    48	                    ->badge()
    49	                    ->formatStateUsing(fn ($state) => $state?->getLabel())
    50	                    ->color(fn ($state) => $state?->getColor()),
    51	
    52	                Tables\Columns\TextColumn::make('created_at')
    53	                    ->label('تاريخ البدء')
    54	                    ->since()
    55	                    ->sortable(),
    56	
    57	                Tables\Columns\TextColumn::make('assigned_to_name')
    58	                    ->label('المسؤول')
    59	                    ->placeholder('غير محدد'),
    60	            ])
    61	            ->paginated(false)
    62	            ->actions([
    63	                Tables\Actions\Action::make('view_member')
    64	                    ->label('عرض العضو')
    65	                    ->icon('heroicon-m-eye')
    66	                    ->url(fn (WorkflowProgress $record) => route('filament.admin.resources.members.view', $record->member_id))
    67	                    ->openUrlInNewTab(false),
    68	            ]);
    69	    }
    70	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [216]: app/Filament/Widgets/PendingActionsWidget.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [217/494]: app/Filament/Widgets/PendingDependentsWidget.php
│ LANGUAGE: php | LINES: 88 | SIZE: 3426 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	declare(strict_types=1);
     4	
     5	namespace App\Filament\Widgets;
     6	
     7	use App\Enums\DependentStatus;
     8	use App\Filament\Resources\DependentResource;
     9	use App\Models\Dependent;
    10	use Filament\Tables;
    11	use Filament\Tables\Table;
    12	use Filament\Widgets\TableWidget;
    13	
    14	final class PendingDependentsWidget extends TableWidget
    15	{
    16	    protected static ?string $heading = 'تابعون بانتظار المراجعة';
    17	
    18	    protected static ?int $sort = 27;
    19	
    20	    protected int|string|array $columnSpan = 'full';
    21	
    22	    protected static ?string $pollingInterval = '30s';
    23	
    24	    public function table(Table $table): Table
    25	    {
    26	        return $table
    27	            ->query(
    28	                Dependent::query()
    29	                    ->whereIn('status', [DependentStatus::PENDING_REVIEW, DependentStatus::PENDING_DOCUMENTS])
    30	                    ->with(['member:id,full_name_ar,membership_number'])
    31	                    ->latest()
    32	                    ->limit(10)
    33	            )
    34	            ->columns([
    35	                Tables\Columns\ImageColumn::make('photo_path')
    36	                    ->label('')
    37	                    ->circular()
    38	                    ->size(35)
    39	                    ->defaultImageUrl(fn ($record) => 'https://ui-avatars.com/api/?name=' . urlencode($record->full_name_ar) . '&background=random&size=70'),
    40	
    41	                Tables\Columns\TextColumn::make('full_name_ar')
    42	                    ->label('التابع')
    43	                    ->weight('bold')
    44	                    ->url(fn ($record) => DependentResource::getUrl('view', ['record' => $record])),
    45	
    46	                Tables\Columns\TextColumn::make('member.full_name_ar')
    47	                    ->label('العضو')
    48	                    ->color('primary'),
    49	
    50	                Tables\Columns\TextColumn::make('relationship')
    51	                    ->label('القرابة')
    52	                    ->formatStateUsing(fn (string $state) => match ($state) {
    53	                        'spouse' => 'زوج/زوجة',
    54	                        'child' => 'ابن/ابنة',
    55	                        'parent' => 'أب/أم',
    56	                        'sibling' => 'أخ/أخت',
    57	                        default => $state,
    58	                    })
    59	                    ->badge(),
    60	
    61	                Tables\Columns\TextColumn::make('status')
    62	                    ->label('الحالة')
    63	                    ->badge()
    64	                    ->formatStateUsing(fn (DependentStatus $state) => $state->getLabel())
    65	                    ->color(fn (DependentStatus $state) => $state->getColor())
    66	                    ->icon(fn (DependentStatus $state) => $state->getIcon()),
    67	
    68	                Tables\Columns\TextColumn::make('created_at')
    69	                    ->label('تاريخ الطلب')
    70	                    ->since()
    71	                    ->sortable(),
    72	            ])
    73	            ->actions([
    74	                Tables\Actions\Action::make('review')
    75	                    ->label('مراجعة')
    76	                    ->icon('heroicon-o-eye')
    77	                    ->url(fn ($record) => DependentResource::getUrl('view', ['record' => $record])),
    78	            ])
    79	            ->emptyStateHeading('لا توجد طلبات معلقة')
    80	            ->emptyStateDescription('جميع طلبات التابعين تمت مراجعتها.')
    81	            ->emptyStateIcon('heroicon-o-check-circle')
    82	            ->paginated(false);
    83	    }
    84	
    85	    public static function canView(): bool
    86	    {
    87	        return auth()->user()?->can('approve_dependent') ?? false;
    88	    }
    89	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [217]: app/Filament/Widgets/PendingDependentsWidget.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [218/494]: app/Filament/Widgets/RecentActivityWidget.php
│ LANGUAGE: php | LINES: 51 | SIZE: 1661 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Filament\Widgets;
     4	
     5	use Filament\Widgets\Widget;
     6	use Filament\Tables;
     7	use Filament\Tables\Table;
     8	use Filament\Tables\Concerns\InteractsWithTable;
     9	use Filament\Tables\Contracts\HasTable;
    10	use Spatie\Activitylog\Models\Activity;
    11	
    12	class RecentActivityWidget extends Widget implements HasTable
    13	{
    14	    use InteractsWithTable;
    15	
    16	    protected static string $view = 'filament.widgets.recent-activity-widget';
    17	
    18	    protected static ?int $sort = 3;
    19	
    20	    protected int | string | array $columnSpan = 'full';
    21	
    22	    public function table(Table $table): Table
    23	    {
    24	        return $table
    25	            ->query(Activity::query()->with(['causer', 'subject']))
    26	            ->columns([
    27	                Tables\Columns\TextColumn::make('description')
    28	                    ->label('الوصف')
    29	                    ->wrap()
    30	                    ->limit(60),
    31	
    32	                Tables\Columns\TextColumn::make('subject_type')
    33	                    ->label('النوع')
    34	                    ->formatStateUsing(fn (?string $state) => $state ? class_basename($state) : '—')
    35	                    ->badge()
    36	                    ->color('info'),
    37	
    38	                Tables\Columns\TextColumn::make('causer.name')
    39	                    ->label('بواسطة')
    40	                    ->placeholder('النظام'),
    41	
    42	                Tables\Columns\TextColumn::make('created_at')
    43	                    ->label('التاريخ')
    44	                    ->since()
    45	                    ->sortable(),
    46	            ])
    47	            ->defaultSort('created_at', 'desc')
    48	            ->paginated([5, 10, 25])
    49	            ->defaultPaginationPageOption(5)
    50	            ->heading('آخر النشاطات');
    51	    }
    52	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [218]: app/Filament/Widgets/RecentActivityWidget.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [219/494]: app/Filament/Widgets/RecentMembersWidget.php
│ LANGUAGE: php | LINES: 65 | SIZE: 2199 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Filament\Widgets;
     4	
     5	use App\Models\Member;
     6	use Filament\Tables;
     7	use Filament\Tables\Table;
     8	use Filament\Widgets\TableWidget as BaseWidget;
     9	
    10	class RecentMembersWidget extends BaseWidget
    11	{
    12	    protected static ?string $heading = 'أحدث الأعضاء المسجلين';
    13	
    14	    protected static ?int $sort = 5;
    15	
    16	    protected int | string | array $columnSpan = 1;
    17	
    18	    protected static ?string $pollingInterval = '60s';
    19	
    20	    public function table(Table $table): Table
    21	    {
    22	        return $table
    23	            ->query(
    24	                Member::with(['membershipType'])
    25	                    ->orderByDesc('created_at')
    26	                    ->limit(8)
    27	            )
    28	            ->columns([
    29	                Tables\Columns\TextColumn::make('membership_number')
    30	                    ->label('رقم العضوية')
    31	                    ->badge()
    32	                    ->color('primary')
    33	                    ->sortable(),
    34	
    35	                Tables\Columns\TextColumn::make('full_name_ar')
    36	                    ->label('الاسم')
    37	                    ->searchable()
    38	                    ->limit(30),
    39	
    40	                Tables\Columns\TextColumn::make('membershipType.name_ar')
    41	                    ->label('نوع العضوية')
    42	                    ->badge()
    43	                    ->color('info'),
    44	
    45	                Tables\Columns\TextColumn::make('membership_status')
    46	                    ->label('الحالة')
    47	                    ->badge()
    48	                    ->formatStateUsing(fn ($state) => $state?->getLabel())
    49	                    ->color(fn ($state) => $state?->getColor()),
    50	
    51	                Tables\Columns\TextColumn::make('join_date')
    52	                    ->label('تاريخ الانضمام')
    53	                    ->date('d/m/Y')
    54	                    ->sortable(),
    55	            ])
    56	            ->paginated(false)
    57	            ->defaultSort('created_at', 'desc')
    58	            ->actions([
    59	                Tables\Actions\Action::make('view')
    60	                    ->label('عرض')
    61	                    ->icon('heroicon-m-eye')
    62	                    ->url(fn (Member $record) => route('filament.admin.resources.members.view', $record))
    63	                    ->openUrlInNewTab(false),
    64	            ]);
    65	    }
    66	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [219]: app/Filament/Widgets/RecentMembersWidget.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [220/494]: app/Filament/Widgets/RecentViolationsWidget.php
│ LANGUAGE: php | LINES: 69 | SIZE: 2570 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	declare(strict_types=1);
     4	
     5	namespace App\Filament\Widgets;
     6	
     7	use App\Enums\ViolationSeverity;
     8	use App\Enums\ViolationStatus;
     9	use App\Filament\Resources\ViolationResource;
    10	use App\Models\Violation;
    11	use Filament\Tables;
    12	use Filament\Tables\Table;
    13	use Filament\Widgets\TableWidget as BaseWidget;
    14	
    15	class RecentViolationsWidget extends BaseWidget
    16	{
    17	    protected static ?string $heading = 'أحدث المخالفات';
    18	
    19	    protected static ?int $sort = 11;
    20	
    21	    protected int|string|array $columnSpan = 'full';
    22	
    23	    public function table(Table $table): Table
    24	    {
    25	        return $table
    26	            ->query(
    27	                Violation::query()
    28	                    ->with(['member', 'violationType'])
    29	                    ->whereNotIn('status', [ViolationStatus::DISMISSED, ViolationStatus::RESOLVED])
    30	                    ->latest('violation_date')
    31	                    ->limit(10)
    32	            )
    33	            ->columns([
    34	                Tables\Columns\TextColumn::make('violation_number')
    35	                    ->label('الرقم')
    36	                    ->weight('bold'),
    37	
    38	                Tables\Columns\TextColumn::make('member.full_name_ar')
    39	                    ->label('العضو'),
    40	
    41	                Tables\Columns\TextColumn::make('title')
    42	                    ->label('العنوان')
    43	                    ->limit(40),
    44	
    45	                Tables\Columns\TextColumn::make('severity')
    46	                    ->label('الخطورة')
    47	                    ->badge()
    48	                    ->formatStateUsing(fn ($state) => $state instanceof ViolationSeverity ? $state->getLabel() : $state)
    49	                    ->color(fn ($state) => $state instanceof ViolationSeverity ? $state->getColor() : 'gray'),
    50	
    51	                Tables\Columns\TextColumn::make('status')
    52	                    ->label('الحالة')
    53	                    ->badge()
    54	                    ->formatStateUsing(fn ($state) => $state instanceof ViolationStatus ? $state->getLabel() : $state)
    55	                    ->color(fn ($state) => $state instanceof ViolationStatus ? $state->getColor() : 'gray'),
    56	
    57	                Tables\Columns\TextColumn::make('violation_date')
    58	                    ->label('التاريخ')
    59	                    ->date('d/m/Y')
    60	                    ->sortable(),
    61	            ])
    62	            ->actions([
    63	                Tables\Actions\Action::make('view')
    64	                    ->label('عرض')
    65	                    ->url(fn (Violation $record) => ViolationResource::getUrl('view', ['record' => $record]))
    66	                    ->icon('heroicon-o-eye'),
    67	            ])
    68	            ->paginated(false);
    69	    }
    70	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [220]: app/Filament/Widgets/RecentViolationsWidget.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [221/494]: app/Filament/Widgets/RevenueByCategoryChart.php
│ LANGUAGE: php | LINES: 66 | SIZE: 1794 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Filament\Widgets;
     4	
     5	use App\Services\Reporting\DashboardStatisticsService;
     6	use Filament\Widgets\ChartWidget;
     7	
     8	class RevenueByCategoryChart extends ChartWidget
     9	{
    10	    protected static ?string $heading = 'الإيرادات حسب الفئة (هذا الشهر)';
    11	
    12	    protected static ?int $sort = 10;
    13	
    14	    protected int | string | array $columnSpan = 1;
    15	
    16	    protected static ?string $maxHeight = '280px';
    17	
    18	    protected static ?string $pollingInterval = '120s';
    19	
    20	    protected function getData(): array
    21	    {
    22	        $service = app(DashboardStatisticsService::class);
    23	        $data = $service->getRevenueByCategoryThisMonth();
    24	
    25	        $colors = [
    26	            'rgb(59, 130, 246)',
    27	            'rgb(16, 185, 129)',
    28	            'rgb(245, 158, 11)',
    29	            'rgb(239, 68, 68)',
    30	            'rgb(139, 92, 246)',
    31	            'rgb(236, 72, 153)',
    32	            'rgb(14, 165, 233)',
    33	            'rgb(249, 115, 22)',
    34	        ];
    35	
    36	        return [
    37	            'datasets' => [
    38	                [
    39	                    'data' => array_map(fn ($item) => $item['total'], $data),
    40	                    'backgroundColor' => array_slice($colors, 0, count($data)),
    41	                    'borderWidth' => 0,
    42	                ],
    43	            ],
    44	            'labels' => array_map(fn ($item) => $item['category'] ?? 'غير محدد', $data),
    45	        ];
    46	    }
    47	
    48	    protected function getType(): string
    49	    {
    50	        return 'pie';
    51	    }
    52	
    53	    protected function getOptions(): array
    54	    {
    55	        return [
    56	            'plugins' => [
    57	                'legend' => [
    58	                    'position' => 'bottom',
    59	                    'labels' => [
    60	                        'padding' => 12,
    61	                        'usePointStyle' => true,
    62	                    ],
    63	                ],
    64	            ],
    65	        ];
    66	    }
    67	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [221]: app/Filament/Widgets/RevenueByCategoryChart.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [222/494]: app/Filament/Widgets/RevenueChart.php
│ LANGUAGE: php | LINES: 76 | SIZE: 2067 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Filament\Widgets;
     4	
     5	use App\Services\Reporting\DashboardStatisticsService;
     6	use Filament\Widgets\ChartWidget;
     7	
     8	class RevenueChart extends ChartWidget
     9	{
    10	    protected static ?string $heading = 'الإيرادات الشهرية';
    11	
    12	    protected static ?int $sort = 3;
    13	
    14	    protected int | string | array $columnSpan = 1;
    15	
    16	    protected static ?string $maxHeight = '280px';
    17	
    18	    protected static ?string $pollingInterval = '120s';
    19	
    20	    public ?string $filter = '12';
    21	
    22	    protected function getFilters(): ?array
    23	    {
    24	        return [
    25	            '6' => 'آخر 6 أشهر',
    26	            '12' => 'آخر 12 شهر',
    27	        ];
    28	    }
    29	
    30	    protected function getData(): array
    31	    {
    32	        $months = (int) $this->filter;
    33	        $service = app(DashboardStatisticsService::class);
    34	        $data = $service->getRevenueChartData($months);
    35	
    36	        return [
    37	            'datasets' => [
    38	                [
    39	                    'label' => 'الإيرادات (ج.م)',
    40	                    'data' => array_map(fn ($item) => $item['revenue'], $data),
    41	                    'fill' => true,
    42	                    'backgroundColor' => 'rgba(16, 185, 129, 0.15)',
    43	                    'borderColor' => 'rgb(16, 185, 129)',
    44	                    'borderWidth' => 2,
    45	                    'tension' => 0.3,
    46	                    'pointRadius' => 4,
    47	                    'pointHoverRadius' => 6,
    48	                ],
    49	            ],
    50	            'labels' => array_map(fn ($item) => $item['month'], $data),
    51	        ];
    52	    }
    53	
    54	    protected function getType(): string
    55	    {
    56	        return 'line';
    57	    }
    58	
    59	    protected function getOptions(): array
    60	    {
    61	        return [
    62	            'scales' => [
    63	                'y' => [
    64	                    'beginAtZero' => true,
    65	                    'ticks' => [
    66	                        'callback' => '(value) => value.toLocaleString() + " ج.م"',
    67	                    ],
    68	                ],
    69	            ],
    70	            'plugins' => [
    71	                'legend' => [
    72	                    'display' => false,
    73	                ],
    74	            ],
    75	        ];
    76	    }
    77	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [222]: app/Filament/Widgets/RevenueChart.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [223/494]: app/Filament/Widgets/StatsOverviewWidget.php
│ LANGUAGE: php | LINES: 90 | SIZE: 3584 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Filament\Widgets;
     4	
     5	use App\Services\Reporting\DashboardStatisticsService;
     6	use Filament\Widgets\StatsOverviewWidget as BaseWidget;
     7	use Filament\Widgets\StatsOverviewWidget\Stat;
     8	
     9	class StatsOverviewWidget extends BaseWidget
    10	{
    11	    protected static ?int $sort = 1;
    12	
    13	    protected int | string | array $columnSpan = 'full';
    14	
    15	    protected static ?string $pollingInterval = '60s';
    16	
    17	    protected function getStats(): array
    18	    {
    19	        $service = app(DashboardStatisticsService::class);
    20	        $stats = $service->getOverviewStats();
    21	
    22	        return [
    23	            Stat::make('إجمالي الأعضاء', number_format($stats['total_members']))
    24	                ->description('أعضاء مسجلون في النظام')
    25	                ->descriptionIcon('heroicon-m-users')
    26	                ->color('primary')
    27	                ->chart($this->getMembershipTrend()),
    28	
    29	            Stat::make('أعضاء نشطون', number_format($stats['active_members']))
    30	                ->description($this->getPercentage($stats['active_members'], $stats['total_members']) . '% من الإجمالي')
    31	                ->descriptionIcon('heroicon-m-check-badge')
    32	                ->color('success'),
    33	
    34	            Stat::make('طلبات معلقة', number_format($stats['pending_members']))
    35	                ->description('في انتظار الإجراء')
    36	                ->descriptionIcon('heroicon-m-clock')
    37	                ->color('warning'),
    38	
    39	            Stat::make('أعضاء موقوفون', number_format($stats['suspended_members']))
    40	                ->description('إيقاف مؤقت')
    41	                ->descriptionIcon('heroicon-m-no-symbol')
    42	                ->color('danger'),
    43	
    44	            Stat::make('إيرادات الشهر', number_format($stats['total_revenue_this_month'], 2) . ' ج.م')
    45	                ->description('الشهر الحالي')
    46	                ->descriptionIcon('heroicon-m-banknotes')
    47	                ->color('success')
    48	                ->chart($this->getRevenueTrend()),
    49	
    50	            Stat::make('إيرادات السنة', number_format($stats['total_revenue_this_year'], 2) . ' ج.م')
    51	                ->description('السنة الحالية')
    52	                ->descriptionIcon('heroicon-m-currency-dollar')
    53	                ->color('info'),
    54	
    55	            Stat::make('أرصدة مستحقة', number_format($stats['outstanding_balance'], 2) . ' ج.م')
    56	                ->description('فواتير غير مسددة')
    57	                ->descriptionIcon('heroicon-m-exclamation-triangle')
    58	                ->color($stats['outstanding_balance'] > 0 ? 'danger' : 'success'),
    59	
    60	            Stat::make('اشتراكات تنتهي قريباً', number_format($stats['expiring_subscriptions_30_days']))
    61	                ->description('خلال 30 يوم')
    62	                ->descriptionIcon('heroicon-m-calendar-days')
    63	                ->color('warning'),
    64	        ];
    65	    }
    66	
    67	    private function getMembershipTrend(): array
    68	    {
    69	        $service = app(DashboardStatisticsService::class);
    70	        $data = $service->getMembershipGrowthData(6);
    71	
    72	        return array_map(fn ($item) => $item['new_members'], $data);
    73	    }
    74	
    75	    private function getRevenueTrend(): array
    76	    {
    77	        $service = app(DashboardStatisticsService::class);
    78	        $data = $service->getRevenueChartData(6);
    79	
    80	        return array_map(fn ($item) => (int) $item['revenue'], $data);
    81	    }
    82	
    83	    private function getPercentage(int $part, int $total): string
    84	    {
    85	        if ($total === 0) {
    86	            return '0';
    87	        }
    88	
    89	        return number_format(($part / $total) * 100, 1);
    90	    }
    91	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [223]: app/Filament/Widgets/StatsOverviewWidget.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [224/494]: app/Filament/Widgets/SubscriptionOverviewWidget.php
│ LANGUAGE: php | LINES: 76 | SIZE: 3086 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Filament\Widgets;
     4	
     5	use App\Enums\SubscriptionStatus;
     6	use App\Models\Subscription;
     7	use App\Models\SubscriptionPeriod;
     8	use Filament\Widgets\StatsOverviewWidget;
     9	use Filament\Widgets\StatsOverviewWidget\Stat;
    10	
    11	class SubscriptionOverviewWidget extends StatsOverviewWidget
    12	{
    13	    protected static ?string $pollingInterval = '60s';
    14	    protected static ?int $sort = 3;
    15	
    16	    protected function getStats(): array
    17	    {
    18	        $currentPeriod = SubscriptionPeriod::active()->first();
    19	
    20	        if (!$currentPeriod) {
    21	            return [
    22	                Stat::make('لا توجد فترة نشطة', 'يرجى إنشاء فترة اشتراك')
    23	                    ->icon('heroicon-o-exclamation-triangle')
    24	                    ->color('warning'),
    25	            ];
    26	        }
    27	
    28	        $query = Subscription::where('subscription_period_id', $currentPeriod->id);
    29	
    30	        $totalCount = (clone $query)->count();
    31	        $paidCount = (clone $query)->where('status', SubscriptionStatus::PAID)->count();
    32	        $overdueCount = (clone $query)->where('status', SubscriptionStatus::OVERDUE)->count();
    33	        $totalCollected = (clone $query)->sum('paid_amount');
    34	        $totalOutstanding = (clone $query)->unpaid()->sum('balance');
    35	        $collectionRate = $totalCount > 0 ? round(($paidCount / $totalCount) * 100, 1) : 0;
    36	
    37	        return [
    38	            Stat::make('اشتراكات ' . $currentPeriod->year, number_format($totalCount))
    39	                ->description('إجمالي الاشتراكات')
    40	                ->icon('heroicon-o-users')
    41	                ->color('info'),
    42	
    43	            Stat::make('نسبة التحصيل', $collectionRate . '%')
    44	                ->description("{$paidCount} مدفوع من {$totalCount}")
    45	                ->icon('heroicon-o-chart-pie')
    46	                ->color($collectionRate >= 80 ? 'success' : ($collectionRate >= 50 ? 'warning' : 'danger'))
    47	                ->chart($this->getCollectionTrend($currentPeriod)),
    48	
    49	            Stat::make('المحصّل', number_format($totalCollected, 2) . ' ج.م')
    50	                ->description('إجمالي المبالغ المحصّلة')
    51	                ->icon('heroicon-o-banknotes')
    52	                ->color('success'),
    53	
    54	            Stat::make('متأخر السداد', number_format($overdueCount))
    55	                ->description(number_format($totalOutstanding, 2) . ' ج.م مستحق')
    56	                ->icon('heroicon-o-exclamation-triangle')
    57	                ->color($overdueCount > 0 ? 'danger' : 'success'),
    58	        ];
    59	    }
    60	
    61	    protected function getCollectionTrend(SubscriptionPeriod $period): array
    62	    {
    63	        // Last 6 months paid count trend
    64	        $data = [];
    65	        for ($i = 5; $i >= 0; $i--) {
    66	            $date = now()->subMonths($i);
    67	            $count = Subscription::where('subscription_period_id', $period->id)
    68	                ->where('status', SubscriptionStatus::PAID)
    69	                ->whereMonth('paid_date', $date->month)
    70	                ->whereYear('paid_date', $date->year)
    71	                ->count();
    72	            $data[] = $count;
    73	        }
    74	
    75	        return $data;
    76	    }
    77	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [224]: app/Filament/Widgets/SubscriptionOverviewWidget.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [225/494]: app/Filament/Widgets/SubscriptionStatusChart.php
│ LANGUAGE: php | LINES: 77 | SIZE: 2225 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Filament\Widgets;
     4	
     5	use App\Enums\SubscriptionStatus;
     6	use App\Models\Subscription;
     7	use App\Models\SubscriptionPeriod;
     8	use Filament\Widgets\ChartWidget;
     9	
    10	class SubscriptionStatusChart extends ChartWidget
    11	{
    12	    protected static ?string $heading = 'توزيع حالات الاشتراكات';
    13	    protected static ?int $sort = 4;
    14	    protected static ?string $maxHeight = '300px';
    15	
    16	    protected function getData(): array
    17	    {
    18	        $currentPeriod = SubscriptionPeriod::active()->first();
    19	
    20	        if (!$currentPeriod) {
    21	            return [
    22	                'datasets' => [['data' => [0]]],
    23	                'labels'   => ['لا توجد فترة نشطة'],
    24	            ];
    25	        }
    26	
    27	        $statusCounts = [];
    28	        $labels = [];
    29	        $colors = [];
    30	
    31	        foreach (SubscriptionStatus::cases() as $status) {
    32	            $count = Subscription::where('subscription_period_id', $currentPeriod->id)
    33	                ->where('status', $status)
    34	                ->count();
    35	
    36	            if ($count > 0) {
    37	                $statusCounts[] = $count;
    38	                $labels[] = $status->label();
    39	                $colors[] = match ($status) {
    40	                    SubscriptionStatus::PENDING   => '#f59e0b',
    41	                    SubscriptionStatus::PARTIAL   => '#3b82f6',
    42	                    SubscriptionStatus::PAID      => '#10b981',
    43	                    SubscriptionStatus::OVERDUE   => '#ef4444',
    44	                    SubscriptionStatus::WAIVED    => '#6b7280',
    45	                    SubscriptionStatus::CANCELLED => '#9ca3af',
    46	                };
    47	            }
    48	        }
    49	
    50	        return [
    51	            'datasets' => [
    52	                [
    53	                    'data'            => $statusCounts,
    54	                    'backgroundColor' => $colors,
    55	                    'borderWidth'     => 0,
    56	                ],
    57	            ],
    58	            'labels' => $labels,
    59	        ];
    60	    }
    61	
    62	    protected function getType(): string
    63	    {
    64	        return 'doughnut';
    65	    }
    66	
    67	    protected function getOptions(): array
    68	    {
    69	        return [
    70	            'plugins' => [
    71	                'legend' => [
    72	                    'position' => 'bottom',
    73	                    'rtl'      => true,
    74	                ],
    75	            ],
    76	        ];
    77	    }
    78	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [225]: app/Filament/Widgets/SubscriptionStatusChart.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [226/494]: app/Filament/Widgets/UpcomingExpirationsWidget.php
│ LANGUAGE: php | LINES: 60 | SIZE: 2243 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Filament\Widgets;
     4	
     5	use App\Enums\SubscriptionStatus;
     6	use App\Models\Subscription;
     7	use Filament\Tables;
     8	use Filament\Tables\Table;
     9	use Filament\Widgets\TableWidget as BaseWidget;
    10	use Illuminate\Support\Carbon;
    11	
    12	class UpcomingExpirationsWidget extends BaseWidget
    13	{
    14	    protected static ?string $heading = 'اشتراكات تنتهي قريباً';
    15	
    16	    protected static ?int $sort = 6;
    17	
    18	    protected int | string | array $columnSpan = 1;
    19	
    20	    protected static ?string $pollingInterval = '120s';
    21	
    22	    public function table(Table $table): Table
    23	    {
    24	        return $table
    25	            ->query(
    26	                Subscription::with(['member', 'subscriptionPlan'])
    27	                    ->where('status', SubscriptionStatus::Active)
    28	                    ->whereBetween('end_date', [Carbon::now(), Carbon::now()->addDays(30)])
    29	                    ->orderBy('end_date')
    30	                    ->limit(10)
    31	            )
    32	            ->columns([
    33	                Tables\Columns\TextColumn::make('member.membership_number')
    34	                    ->label('رقم العضوية')
    35	                    ->badge()
    36	                    ->color('primary'),
    37	
    38	                Tables\Columns\TextColumn::make('member.full_name_ar')
    39	                    ->label('العضو')
    40	                    ->limit(25),
    41	
    42	                Tables\Columns\TextColumn::make('subscriptionPlan.name_ar')
    43	                    ->label('الخطة')
    44	                    ->badge()
    45	                    ->color('info'),
    46	
    47	                Tables\Columns\TextColumn::make('end_date')
    48	                    ->label('تاريخ الانتهاء')
    49	                    ->date('d/m/Y')
    50	                    ->color(fn (Subscription $record) => $record->end_date->diffInDays(Carbon::now()) <= 7 ? 'danger' : 'warning')
    51	                    ->sortable(),
    52	
    53	                Tables\Columns\TextColumn::make('days_remaining')
    54	                    ->label('متبقي')
    55	                    ->state(fn (Subscription $record) => $record->end_date->diffInDays(Carbon::now()) . ' يوم')
    56	                    ->badge()
    57	                    ->color(fn (Subscription $record) => $record->end_date->diffInDays(Carbon::now()) <= 7 ? 'danger' : 'warning'),
    58	            ])
    59	            ->paginated(false);
    60	    }
    61	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [226]: app/Filament/Widgets/UpcomingExpirationsWidget.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [227/494]: app/Filament/Widgets/ViolationsOverviewWidget.php
│ LANGUAGE: php | LINES: 46 | SIZE: 1780 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Filament\Widgets;
     4	
     5	use App\Services\Reporting\DashboardStatisticsService;
     6	use Filament\Widgets\StatsOverviewWidget as BaseWidget;
     7	use Filament\Widgets\StatsOverviewWidget\Stat;
     8	
     9	class ViolationsOverviewWidget extends BaseWidget
    10	{
    11	    protected static ?string $heading = 'المخالفات والجزاءات';
    12	
    13	    protected static ?int $sort = 8;
    14	
    15	    protected int | string | array $columnSpan = 'full';
    16	
    17	    protected static ?string $pollingInterval = '120s';
    18	
    19	    protected function getStats(): array
    20	    {
    21	        $service = app(DashboardStatisticsService::class);
    22	        $overview = $service->getOverviewStats();
    23	        $violations = $service->getViolationsSummary();
    24	
    25	        return [
    26	            Stat::make('مخالفات معلقة', number_format($overview['pending_violations']))
    27	                ->description('في انتظار التحقيق')
    28	                ->descriptionIcon('heroicon-m-exclamation-triangle')
    29	                ->color('warning'),
    30	
    31	            Stat::make('إيقافات نشطة', number_format($overview['active_suspensions']))
    32	                ->description('أعضاء موقوفون حالياً')
    33	                ->descriptionIcon('heroicon-m-no-symbol')
    34	                ->color('danger'),
    35	
    36	            Stat::make('مخالفات هذا الشهر', number_format($violations['total_this_month']))
    37	                ->description('الشهر الحالي')
    38	                ->descriptionIcon('heroicon-m-flag')
    39	                ->color('info'),
    40	
    41	            Stat::make('مخالفات هذا العام', number_format($violations['total_this_year']))
    42	                ->description('السنة الحالية')
    43	                ->descriptionIcon('heroicon-m-chart-bar')
    44	                ->color('primary'),
    45	        ];
    46	    }
    47	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [227]: app/Filament/Widgets/ViolationsOverviewWidget.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [228/494]: app/Helpers/DateHelper.php
│ LANGUAGE: php | LINES: 80 | SIZE: 2043 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Helpers;
     4	
     5	use Carbon\Carbon;
     6	
     7	class DateHelper
     8	{
     9	    public static function format(?string $date, string $format = null): string
    10	    {
    11	        if ($date === null) {
    12	            return '';
    13	        }
    14	
    15	        $format = $format ?? config('club.date_format', 'd/m/Y');
    16	
    17	        return Carbon::parse($date)->format($format);
    18	    }
    19	
    20	    public static function getCurrentFiscalYear(): int
    21	    {
    22	        $now = Carbon::now();
    23	        $startMonth = config('club.fiscal_year_start_month', 1);
    24	
    25	        if ($now->month < $startMonth) {
    26	            return $now->year - 1;
    27	        }
    28	
    29	        return $now->year;
    30	    }
    31	
    32	    public static function getFiscalYearRange(int $year): array
    33	    {
    34	        $startMonth = config('club.fiscal_year_start_month', 1);
    35	        $startDay = config('club.fiscal_year_start_day', 1);
    36	
    37	        $start = Carbon::create($year, $startMonth, $startDay)->startOfDay();
    38	        $end = $start->copy()->addYear()->subDay()->endOfDay();
    39	
    40	        return ['start' => $start, 'end' => $end];
    41	    }
    42	
    43	    public static function age(?string $dateOfBirth): ?int
    44	    {
    45	        if ($dateOfBirth === null) {
    46	            return null;
    47	        }
    48	
    49	        return Carbon::parse($dateOfBirth)->age;
    50	    }
    51	
    52	    public static function isExpired(?string $date): bool
    53	    {
    54	        if ($date === null) {
    55	            return true;
    56	        }
    57	
    58	        return Carbon::parse($date)->isPast();
    59	    }
    60	
    61	    public static function daysUntil(?string $date): ?int
    62	    {
    63	        if ($date === null) {
    64	            return null;
    65	        }
    66	
    67	        return (int) Carbon::now()->diffInDays(Carbon::parse($date), false);
    68	    }
    69	
    70	    public static function arabicMonth(int $month): string
    71	    {
    72	        $months = [
    73	            1 => 'يناير', 2 => 'فبراير', 3 => 'مارس',
    74	            4 => 'أبريل', 5 => 'مايو', 6 => 'يونيو',
    75	            7 => 'يوليو', 8 => 'أغسطس', 9 => 'سبتمبر',
    76	            10 => 'أكتوبر', 11 => 'نوفمبر', 12 => 'ديسمبر',
    77	        ];
    78	
    79	        return $months[$month] ?? '';
    80	    }
    81	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [228]: app/Helpers/DateHelper.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [229/494]: app/Helpers/MoneyHelper.php
│ LANGUAGE: php | LINES: 112 | SIZE: 3633 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Helpers;
     4	
     5	class MoneyHelper
     6	{
     7	    public static function format(float|int|null $amount, bool $withCurrency = true): string
     8	    {
     9	        if ($amount === null) {
    10	            return '0.00';
    11	        }
    12	
    13	        $formatted = number_format((float) $amount, 2, '.', ',');
    14	
    15	        if ($withCurrency) {
    16	            $currency = config('club.currency_name_ar', 'جنيه');
    17	            return "{$formatted} {$currency}";
    18	        }
    19	
    20	        return $formatted;
    21	    }
    22	
    23	    public static function formatCompact(float|int|null $amount): string
    24	    {
    25	        if ($amount === null) {
    26	            return '0';
    27	        }
    28	
    29	        $amount = (float) $amount;
    30	
    31	        if ($amount >= 1_000_000) {
    32	            return round($amount / 1_000_000, 1) . 'M';
    33	        }
    34	
    35	        if ($amount >= 1_000) {
    36	            return round($amount / 1_000, 1) . 'K';
    37	        }
    38	
    39	        return number_format($amount, 2);
    40	    }
    41	
    42	    public static function toWords(float|int $amount): string
    43	    {
    44	        $currency = config('club.currency_name_ar', 'جنيه');
    45	        $subunit = config('club.currency_subunit_ar', 'قرش');
    46	
    47	        $integer = (int) floor($amount);
    48	        $fraction = (int) round(($amount - $integer) * 100);
    49	
    50	        $result = self::numberToArabicWords($integer) . " {$currency}";
    51	
    52	        if ($fraction > 0) {
    53	            $result .= ' و ' . self::numberToArabicWords($fraction) . " {$subunit}";
    54	        }
    55	
    56	        return $result . ' فقط لا غير';
    57	    }
    58	
    59	    private static function numberToArabicWords(int $number): string
    60	    {
    61	        if ($number === 0) {
    62	            return 'صفر';
    63	        }
    64	
    65	        $ones = ['', 'واحد', 'اثنان', 'ثلاثة', 'أربعة', 'خمسة', 'ستة', 'سبعة', 'ثمانية', 'تسعة',
    66	            'عشرة', 'أحد عشر', 'اثنا عشر', 'ثلاثة عشر', 'أربعة عشر', 'خمسة عشر',
    67	            'ستة عشر', 'سبعة عشر', 'ثمانية عشر', 'تسعة عشر'];
    68	        $tens = ['', '', 'عشرون', 'ثلاثون', 'أربعون', 'خمسون', 'ستون', 'سبعون', 'ثمانون', 'تسعون'];
    69	        $hundreds = ['', 'مائة', 'مئتان', 'ثلاثمائة', 'أربعمائة', 'خمسمائة', 'ستمائة', 'سبعمائة', 'ثمانمائة', 'تسعمائة'];
    70	
    71	        $parts = [];
    72	
    73	        if ($number >= 1000000) {
    74	            $millions = (int) floor($number / 1000000);
    75	            $parts[] = ($millions === 1 ? 'مليون' : ($millions === 2 ? 'مليونان' : self::numberToArabicWords($millions) . ' ملايين'));
    76	            $number %= 1000000;
    77	        }
    78	
    79	        if ($number >= 1000) {
    80	            $thousands = (int) floor($number / 1000);
    81	            $parts[] = ($thousands === 1 ? 'ألف' : ($thousands === 2 ? 'ألفان' : self::numberToArabicWords($thousands) . ' آلاف'));
    82	            $number %= 1000;
    83	        }
    84	
    85	        if ($number >= 100) {
    86	            $parts[] = $hundreds[(int) floor($number / 100)];
    87	            $number %= 100;
    88	        }
    89	
    90	        if ($number >= 20) {
    91	            $remainder = $number % 10;
    92	            if ($remainder > 0) {
    93	                $parts[] = $ones[$remainder] . ' و' . $tens[(int) floor($number / 10)];
    94	            } else {
    95	                $parts[] = $tens[(int) floor($number / 10)];
    96	            }
    97	        } elseif ($number > 0) {
    98	            $parts[] = $ones[$number];
    99	        }
   100	
   101	        return implode(' و', $parts);
   102	    }
   103	
   104	    public static function centsToAmount(int $cents): float
   105	    {
   106	        return round($cents / 100, 2);
   107	    }
   108	
   109	    public static function amountToCents(float $amount): int
   110	    {
   111	        return (int) round($amount * 100);
   112	    }
   113	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [229]: app/Helpers/MoneyHelper.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [230/494]: app/Helpers/NationalIdHelper.php
│ LANGUAGE: php | LINES: 76 | SIZE: 1883 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Helpers;
     4	
     5	use Carbon\Carbon;
     6	
     7	class NationalIdHelper
     8	{
     9	    /**
    10	     * Validate Egyptian National ID (14 digits)
    11	     */
    12	    public static function isValid(?string $nationalId): bool
    13	    {
    14	        if ($nationalId === null || strlen($nationalId) !== 14) {
    15	            return false;
    16	        }
    17	
    18	        if (!preg_match('/^[23]\d{13}$/', $nationalId)) {
    19	            return false;
    20	        }
    21	
    22	        $century = (int) $nationalId[0];
    23	        $year = (int) substr($nationalId, 1, 2);
    24	        $month = (int) substr($nationalId, 3, 2);
    25	        $day = (int) substr($nationalId, 5, 2);
    26	
    27	        if ($month < 1 || $month > 12 || $day < 1 || $day > 31) {
    28	            return false;
    29	        }
    30	
    31	        $fullYear = ($century === 2 ? 1900 : 2000) + $year;
    32	
    33	        try {
    34	            Carbon::createStrict($fullYear, $month, $day);
    35	        } catch (\Exception) {
    36	            return false;
    37	        }
    38	
    39	        return true;
    40	    }
    41	
    42	    public static function extractDateOfBirth(string $nationalId): ?Carbon
    43	    {
    44	        if (!self::isValid($nationalId)) {
    45	            return null;
    46	        }
    47	
    48	        $century = (int) $nationalId[0];
    49	        $year = (int) substr($nationalId, 1, 2);
    50	        $month = (int) substr($nationalId, 3, 2);
    51	        $day = (int) substr($nationalId, 5, 2);
    52	
    53	        $fullYear = ($century === 2 ? 1900 : 2000) + $year;
    54	
    55	        return Carbon::create($fullYear, $month, $day);
    56	    }
    57	
    58	    public static function extractGender(string $nationalId): ?string
    59	    {
    60	        if (!self::isValid($nationalId)) {
    61	            return null;
    62	        }
    63	
    64	        $genderDigit = (int) $nationalId[12];
    65	
    66	        return $genderDigit % 2 === 0 ? 'female' : 'male';
    67	    }
    68	
    69	    public static function extractGovernorateCode(string $nationalId): ?string
    70	    {
    71	        if (!self::isValid($nationalId)) {
    72	            return null;
    73	        }
    74	
    75	        return substr($nationalId, 7, 2);
    76	    }
    77	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [230]: app/Helpers/NationalIdHelper.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [231/494]: app/Helpers/SettingsHelper.php
│ LANGUAGE: php | LINES: 62 | SIZE: 1475 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Helpers;
     4	
     5	use App\Services\Admin\SettingsService;
     6	
     7	class SettingsHelper
     8	{
     9	    public static function get(string $key, mixed $default = null): mixed
    10	    {
    11	        return app(SettingsService::class)->get($key, $default);
    12	    }
    13	
    14	    public static function getClubName(): string
    15	    {
    16	        return self::get('general.club_name_ar', 'النادي');
    17	    }
    18	
    19	    public static function getClubNameEn(): string
    20	    {
    21	        return self::get('general.club_name_en', 'Club');
    22	    }
    23	
    24	    public static function getCurrency(): string
    25	    {
    26	        return self::get('financial.currency', 'EGP');
    27	    }
    28	
    29	    public static function getDateFormat(): string
    30	    {
    31	        return self::get('system.date_format', 'd/m/Y');
    32	    }
    33	
    34	    public static function getTimezone(): string
    35	    {
    36	        return self::get('system.timezone', 'Africa/Cairo');
    37	    }
    38	
    39	    public static function getRowsPerPage(): int
    40	    {
    41	        return (int) self::get('system.rows_per_page', 25);
    42	    }
    43	
    44	    public static function isMaintenanceMode(): bool
    45	    {
    46	        return self::get('system.maintenance_mode', false);
    47	    }
    48	
    49	    public static function isBackupEnabled(): bool
    50	    {
    51	        return self::get('system.backup_enabled', true);
    52	    }
    53	
    54	    public static function isSmsEnabled(): bool
    55	    {
    56	        return self::get('notification.sms_enabled', false);
    57	    }
    58	
    59	    public static function isEmailEnabled(): bool
    60	    {
    61	        return self::get('notification.email_enabled', false);
    62	    }
    63	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [231]: app/Helpers/SettingsHelper.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [232/494]: app/Http/Controllers/Api/V1/CardVerificationController.php
│ LANGUAGE: php | LINES: 44 | SIZE: 1621 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	declare(strict_types=1);
     4	
     5	namespace App\Http\Controllers\Api\V1;
     6	
     7	use App\Http\Controllers\Controller;
     8	use App\Models\Card;
     9	use Illuminate\Http\JsonResponse;
    10	
    11	class CardVerificationController extends Controller
    12	{
    13	    public function verify(string $qrCode): JsonResponse
    14	    {
    15	        $card = Card::where('qr_code_data', $qrCode)
    16	            ->where('status', 'active')
    17	            ->with(['member:id,membership_number,name_ar,status_id', 'member.membershipStatus:id,code,name_ar'])
    18	            ->first();
    19	
    20	        if (! $card) {
    21	            return response()->json([
    22	                'valid' => false,
    23	                'message' => 'بطاقة غير صالحة أو غير موجودة',
    24	            ], 404);
    25	        }
    26	
    27	        $memberActive = $card->member?->membershipStatus?->code === 'ACTIVE';
    28	        $cardValid = $card->status === 'active' && $card->valid_for_year >= (int) now()->format('Y');
    29	
    30	        return response()->json([
    31	            'valid' => $memberActive && $cardValid,
    32	            'card_number' => $card->card_number,
    33	            'member_name' => $card->member?->name_ar,
    34	            'membership_number' => $card->member?->membership_number,
    35	            'membership_status' => $card->member?->membershipStatus?->name_ar,
    36	            'card_status' => $card->status,
    37	            'valid_for_year' => $card->valid_for_year,
    38	            'message' => match (true) {
    39	                ! $memberActive => 'العضوية غير فعالة',
    40	                ! $cardValid => 'البطاقة منتهية الصلاحية',
    41	                default => 'بطاقة صالحة',
    42	            },
    43	        ]);
    44	    }
    45	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [232]: app/Http/Controllers/Api/V1/CardVerificationController.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [233/494]: app/Http/Controllers/Api/V1/MemberLookupController.php
│ LANGUAGE: php | LINES: 70 | SIZE: 2582 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	declare(strict_types=1);
     4	
     5	namespace App\Http\Controllers\Api\V1;
     6	
     7	use App\Http\Controllers\Controller;
     8	use App\Models\Member;
     9	use Illuminate\Http\JsonResponse;
    10	
    11	class MemberLookupController extends Controller
    12	{
    13	    public function show(string $membershipNumber): JsonResponse
    14	    {
    15	        $member = Member::where('membership_number', $membershipNumber)
    16	            ->where('is_archived', false)
    17	            ->with([
    18	                'membershipType:id,name_ar,name_en,code',
    19	                'membershipStatus:id,name_ar,name_en,code,color',
    20	                'dependents' => fn ($q) => $q->where('is_archived', false)->select('id', 'member_id', 'name_ar', 'relation', 'status'),
    21	            ])
    22	            ->first();
    23	
    24	        if (! $member) {
    25	            return response()->json([
    26	                'message' => 'عضو غير موجود',
    27	            ], 404);
    28	        }
    29	
    30	        return response()->json([
    31	            'membership_number' => $member->membership_number,
    32	            'name_ar' => $member->name_ar,
    33	            'name_en' => $member->name_en,
    34	            'membership_type' => $member->membershipType?->name_ar,
    35	            'status' => $member->membershipStatus?->name_ar,
    36	            'status_code' => $member->membershipStatus?->code,
    37	            'activation_date' => $member->activation_date?->format('Y-m-d'),
    38	            'last_renewal_year' => $member->last_renewal_year,
    39	            'dependents_count' => $member->dependents->count(),
    40	            'dependents' => $member->dependents->map(fn ($d) => [
    41	                'name' => $d->name_ar,
    42	                'relation' => $d->relation,
    43	                'status' => $d->status,
    44	            ]),
    45	        ]);
    46	    }
    47	
    48	    public function status(string $membershipNumber): JsonResponse
    49	    {
    50	        $member = Member::where('membership_number', $membershipNumber)
    51	            ->where('is_archived', false)
    52	            ->with('membershipStatus:id,name_ar,code,allows_entry')
    53	            ->first(['id', 'membership_number', 'name_ar', 'status_id']);
    54	
    55	        if (! $member) {
    56	            return response()->json([
    57	                'found' => false,
    58	                'message' => 'عضو غير موجود',
    59	            ], 404);
    60	        }
    61	
    62	        return response()->json([
    63	            'found' => true,
    64	            'membership_number' => $member->membership_number,
    65	            'name' => $member->name_ar,
    66	            'status' => $member->membershipStatus?->name_ar,
    67	            'status_code' => $member->membershipStatus?->code,
    68	            'allows_entry' => (bool) $member->membershipStatus?->allows_entry,
    69	        ]);
    70	    }
    71	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [233]: app/Http/Controllers/Api/V1/MemberLookupController.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [234/494]: app/Http/Controllers/CardController.php
│ LANGUAGE: php | LINES: 59 | SIZE: 1660 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	declare(strict_types=1);
     4	
     5	namespace App\Http\Controllers;
     6	
     7	use App\Models\Card;
     8	use App\Services\Cards\CardPrintService;
     9	use Illuminate\Http\Request;
    10	use Illuminate\Http\Response;
    11	use Illuminate\View\View;
    12	use Symfony\Component\HttpFoundation\StreamedResponse;
    13	
    14	class CardController extends Controller
    15	{
    16	    public function __construct(
    17	        private readonly CardPrintService $cardPrintService,
    18	    ) {}
    19	
    20	    public function preview(Card $card): View
    21	    {
    22	        $this->authorize('view', $card);
    23	
    24	        $card->load(['member', 'member.membershipType', 'dependent']);
    25	
    26	        return view('pdf.member-card', [
    27	            'card' => $card,
    28	            'member' => $card->member,
    29	            'dependent' => $card->dependent,
    30	            'isPreview' => true,
    31	        ]);
    32	    }
    33	
    34	    public function print(Card $card): StreamedResponse|Response
    35	    {
    36	        $this->authorize('print', $card);
    37	
    38	        $card->load(['member', 'member.membershipType', 'dependent']);
    39	
    40	        $pdf = $this->cardPrintService->generatePdf($card);
    41	
    42	        activity()
    43	            ->performedOn($card)
    44	            ->causedBy(auth()->user())
    45	            ->withProperties([
    46	                'card_number' => $card->card_number,
    47	                'member_id' => $card->member_id,
    48	                'action' => 'print',
    49	            ])
    50	            ->log('طباعة بطاقة عضوية');
    51	
    52	        $filename = sprintf('card_%s_%s.pdf', $card->card_number, now()->format('Ymd'));
    53	
    54	        return response()->streamDownload(
    55	            callback: fn () => print($pdf->output()),
    56	            name: $filename,
    57	            headers: ['Content-Type' => 'application/pdf'],
    58	        );
    59	    }
    60	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [234]: app/Http/Controllers/CardController.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [235/494]: app/Http/Controllers/MemberPrintController.php
│ LANGUAGE: php | LINES: 46 | SIZE: 1286 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Http\Controllers;
     4	
     5	use App\Models\Member;
     6	use Barryvdh\DomPDF\Facade\Pdf;
     7	use Illuminate\Http\Response;
     8	
     9	class MemberPrintController extends Controller
    10	{
    11	    public function profile(Member $member): Response
    12	    {
    13	        $member->load([
    14	            'membershipType',
    15	            'dependents',
    16	            'receipts',
    17	            'subscriptions',
    18	            'violations',
    19	            'referee1',
    20	            'referee2',
    21	        ]);
    22	
    23	        $pdf = Pdf::loadView('prints.member-profile', [
    24	            'member' => $member,
    25	        ])
    26	            ->setPaper('a4')
    27	            ->setOption('isHtml5ParserEnabled', true)
    28	            ->setOption('isRemoteEnabled', true)
    29	            ->setOption('defaultFont', 'Cairo');
    30	
    31	        return $pdf->stream("member-{$member->membership_number}-profile.pdf");
    32	    }
    33	
    34	    public function card(Member $member): Response
    35	    {
    36	        $member->load(['membershipType']);
    37	
    38	        $pdf = Pdf::loadView('prints.member-card', [
    39	            'member' => $member,
    40	        ])
    41	            ->setPaper([0, 0, 242.65, 153.01]) // CR80 card size in points
    42	            ->setOption('isHtml5ParserEnabled', true)
    43	            ->setOption('isRemoteEnabled', true);
    44	
    45	        return $pdf->stream("member-{$member->membership_number}-card.pdf");
    46	    }
    47	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [235]: app/Http/Controllers/MemberPrintController.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [236/494]: app/Http/Controllers/ReceiptController.php
│ LANGUAGE: php | LINES: 71 | SIZE: 1919 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	declare(strict_types=1);
     4	
     5	namespace App\Http\Controllers;
     6	
     7	use App\Models\Receipt;
     8	use App\Services\Financial\ReceiptService;
     9	use Illuminate\Http\Response;
    10	use Illuminate\View\View;
    11	use Symfony\Component\HttpFoundation\StreamedResponse;
    12	
    13	class ReceiptController extends Controller
    14	{
    15	    public function __construct(
    16	        private readonly ReceiptService $receiptService,
    17	    ) {}
    18	
    19	    public function show(Receipt $receipt): View
    20	    {
    21	        $this->authorize('view', $receipt);
    22	
    23	        $receipt->load([
    24	            'member',
    25	            'receiptItems',
    26	            'paymentMethod',
    27	            'cashRegister',
    28	            'createdBy',
    29	        ]);
    30	
    31	        return view('pdf.receipt', [
    32	            'receipt' => $receipt,
    33	            'member' => $receipt->member,
    34	            'items' => $receipt->receiptItems,
    35	            'isPreview' => true,
    36	        ]);
    37	    }
    38	
    39	    public function print(Receipt $receipt): StreamedResponse|Response
    40	    {
    41	        $this->authorize('print', $receipt);
    42	
    43	        $receipt->load([
    44	            'member',
    45	            'receiptItems',
    46	            'paymentMethod',
    47	            'cashRegister',
    48	            'createdBy',
    49	        ]);
    50	
    51	        $pdf = $this->receiptService->generatePdf($receipt);
    52	
    53	        activity()
    54	            ->performedOn($receipt)
    55	            ->causedBy(auth()->user())
    56	            ->withProperties([
    57	                'receipt_number' => $receipt->receipt_number,
    58	                'member_id' => $receipt->member_id,
    59	                'amount' => $receipt->total_amount,
    60	                'action' => 'print',
    61	            ])
    62	            ->log('طباعة إيصال');
    63	
    64	        $filename = sprintf('receipt_%s_%s.pdf', $receipt->receipt_number, now()->format('Ymd'));
    65	
    66	        return response()->streamDownload(
    67	            callback: fn () => print($pdf->output()),
    68	            name: $filename,
    69	            headers: ['Content-Type' => 'application/pdf'],
    70	        );
    71	    }
    72	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [236]: app/Http/Controllers/ReceiptController.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [237/494]: app/Http/Middleware/CheckUserActive.php
│ LANGUAGE: php | LINES: 24 | SIZE: 633 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Http\Middleware;
     4	
     5	use Closure;
     6	use Illuminate\Http\Request;
     7	use Symfony\Component\HttpFoundation\Response;
     8	
     9	class CheckUserActive
    10	{
    11	    public function handle(Request $request, Closure $next): Response
    12	    {
    13	        if (auth()->check() && !auth()->user()->is_active) {
    14	            auth()->logout();
    15	
    16	            $request->session()->invalidate();
    17	            $request->session()->regenerateToken();
    18	
    19	            return redirect()->route('filament.admin.auth.login')
    20	                ->with('error', 'تم تعطيل حسابك. تواصل مع المسؤول.');
    21	        }
    22	
    23	        return $next($request);
    24	    }
    25	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [237]: app/Http/Middleware/CheckUserActive.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [238/494]: app/Http/Middleware/TrackLastLogin.php
│ LANGUAGE: php | LINES: 25 | SIZE: 612 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Http\Middleware;
     4	
     5	use Closure;
     6	use Illuminate\Http\Request;
     7	use Symfony\Component\HttpFoundation\Response;
     8	
     9	class TrackLastLogin
    10	{
    11	    public function handle(Request $request, Closure $next): Response
    12	    {
    13	        if (auth()->check()) {
    14	            $user = auth()->user();
    15	
    16	            if (!$user->last_login_at || $user->last_login_at->diffInMinutes(now()) > 30) {
    17	                $user->updateQuietly([
    18	                    'last_login_at' => now(),
    19	                    'last_login_ip' => $request->ip(),
    20	                ]);
    21	            }
    22	        }
    23	
    24	        return $next($request);
    25	    }
    26	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [238]: app/Http/Middleware/TrackLastLogin.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [239/494]: app/Imports/MembersImport.php
│ LANGUAGE: php | LINES: 252 | SIZE: 10215 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	declare(strict_types=1);
     4	
     5	namespace App\Imports;
     6	
     7	use App\Enums\Gender;
     8	use App\Helpers\NationalIdHelper;
     9	use App\Models\Governorate;
    10	use App\Models\MaritalStatus;
    11	use App\Models\Member;
    12	use App\Models\MembershipStatus;
    13	use App\Models\MembershipType;
    14	use App\Models\Nationality;
    15	use App\Models\Religion;
    16	use Illuminate\Contracts\Queue\ShouldQueue;
    17	use Illuminate\Support\Collection;
    18	use Illuminate\Support\Facades\DB;
    19	use Illuminate\Support\Facades\Log;
    20	use Illuminate\Support\Facades\Validator;
    21	use Maatwebsite\Excel\Concerns\Importable;
    22	use Maatwebsite\Excel\Concerns\SkipsEmptyRows;
    23	use Maatwebsite\Excel\Concerns\ToCollection;
    24	use Maatwebsite\Excel\Concerns\WithBatchInserts;
    25	use Maatwebsite\Excel\Concerns\WithChunkReading;
    26	use Maatwebsite\Excel\Concerns\WithHeadingRow;
    27	use Maatwebsite\Excel\Concerns\WithValidation;
    28	
    29	class MembersImport implements ToCollection, WithHeadingRow, WithBatchInserts, WithChunkReading, SkipsEmptyRows, WithValidation
    30	{
    31	    use Importable;
    32	
    33	    private int $successCount = 0;
    34	    private int $failCount = 0;
    35	    private array $errors = [];
    36	
    37	    /** Cached lookup maps — loaded once */
    38	    private Collection $membershipTypes;
    39	    private Collection $membershipStatuses;
    40	    private Collection $nationalities;
    41	    private Collection $religions;
    42	    private Collection $maritalStatuses;
    43	    private Collection $governorates;
    44	
    45	    public function __construct(
    46	        private readonly int $importedBy,
    47	    ) {
    48	        $this->membershipTypes = MembershipType::pluck('id', 'name_ar');
    49	        $this->membershipStatuses = MembershipStatus::pluck('id', 'name_ar');
    50	        $this->nationalities = Nationality::pluck('id', 'name_ar');
    51	        $this->religions = Religion::pluck('id', 'name_ar');
    52	        $this->maritalStatuses = MaritalStatus::pluck('id', 'name_ar');
    53	        $this->governorates = Governorate::pluck('id', 'name_ar');
    54	    }
    55	
    56	    public function collection(Collection $rows): void
    57	    {
    58	        foreach ($rows as $index => $row) {
    59	            $rowNumber = $index + 2; // +2 for heading row + 0-index
    60	
    61	            try {
    62	                DB::transaction(function () use ($row, $rowNumber) {
    63	                    $nationalIdData = NationalIdHelper::parse($row['national_id'] ?? '');
    64	
    65	                    $member = Member::create([
    66	                        // — Identity —
    67	                        'membership_number'       => trim((string) ($row['membership_number'] ?? '')),
    68	                        'name_ar'                 => trim((string) ($row['name_ar'] ?? '')),
    69	                        'name_en'                 => trim((string) ($row['name_en'] ?? '')),
    70	                        'national_id'             => trim((string) ($row['national_id'] ?? '')),
    71	
    72	                        // — Date of Birth (from national ID or explicit column) —
    73	                        'date_of_birth'           => $nationalIdData['date_of_birth']
    74	                                                     ?? ($row['date_of_birth'] ?? null),
    75	                        'gender'                  => $this->resolveGender($row, $nationalIdData),
    76	
    77	                        // — Contact —
    78	                        'phone_primary'           => trim((string) ($row['phone_primary'] ?? $row['mobile'] ?? '')),
    79	                        'phone_secondary'         => trim((string) ($row['phone_secondary'] ?? '')),
    80	                        'email'                   => trim((string) ($row['email'] ?? '')),
    81	
    82	                        // — Address —
    83	                        'address_street'          => trim((string) ($row['address_street'] ?? $row['address'] ?? '')),
    84	                        'address_city'            => trim((string) ($row['address_city'] ?? '')),
    85	                        'address_district'        => trim((string) ($row['address_district'] ?? '')),
    86	                        'address_governorate'     => trim((string) ($row['address_governorate'] ?? '')),
    87	                        'address_postal_code'     => trim((string) ($row['address_postal_code'] ?? '')),
    88	
    89	                        // — Employment —
    90	                        'employer_name'           => trim((string) ($row['employer_name'] ?? $row['workplace'] ?? '')),
    91	                        'job_title'               => trim((string) ($row['job_title'] ?? '')),
    92	
    93	                        // — Foreign keys (resolved from lookup tables) —
    94	                        'membership_type_id'      => $this->resolveLookup(
    95	                            $this->membershipTypes,
    96	                            $row['membership_type'] ?? null,
    97	                            'نوع العضوية',
    98	                            $rowNumber,
    99	                        ),
   100	                        'status_id'               => $this->resolveLookup(
   101	                            $this->membershipStatuses,
   102	                            $row['membership_status'] ?? $row['status'] ?? null,
   103	                            'حالة العضوية',
   104	                            $rowNumber,
   105	                        ),
   106	                        'nationality_id'          => $this->resolveLookup(
   107	                            $this->nationalities,
   108	                            $row['nationality'] ?? null,
   109	                            'الجنسية',
   110	                            $rowNumber,
   111	                            required: false,
   112	                        ),
   113	                        'religion_id'             => $this->resolveLookup(
   114	                            $this->religions,
   115	                            $row['religion'] ?? null,
   116	                            'الديانة',
   117	                            $rowNumber,
   118	                            required: false,
   119	                        ),
   120	                        'marital_status_id'       => $this->resolveLookup(
   121	                            $this->maritalStatuses,
   122	                            $row['marital_status'] ?? null,
   123	                            'الحالة الاجتماعية',
   124	                            $rowNumber,
   125	                            required: false,
   126	                        ),
   127	                        'governorate_id'          => $this->resolveLookup(
   128	                            $this->governorates,
   129	                            $row['governorate'] ?? null,
   130	                            'المحافظة',
   131	                            $rowNumber,
   132	                            required: false,
   133	                        ),
   134	
   135	                        // — Dates —
   136	                        'application_date'        => $row['application_date'] ?? now()->toDateString(),
   137	                        'approval_date'           => $row['approval_date'] ?? null,
   138	                        'activation_date'         => $row['activation_date'] ?? null,
   139	
   140	                        // — Meta —
   141	                        'notes'                   => trim((string) ($row['notes'] ?? '')),
   142	                        'created_by'              => $this->importedBy,
   143	                        'is_archived'             => false,
   144	                    ]);
   145	
   146	                    $this->successCount++;
   147	                });
   148	            } catch (\Throwable $e) {
   149	                $this->failCount++;
   150	                $this->errors[] = [
   151	                    'row' => $rowNumber,
   152	                    'message' => $e->getMessage(),
   153	                ];
   154	
   155	                Log::warning("MembersImport: Row {$rowNumber} failed", [
   156	                    'error' => $e->getMessage(),
   157	                    'row_data' => $row->toArray(),
   158	                ]);
   159	            }
   160	        }
   161	    }
   162	
   163	    public function rules(): array
   164	    {
   165	        return [
   166	            'membership_number' => ['required', 'string', 'max:50'],
   167	            'name_ar'           => ['required', 'string', 'max:255'],
   168	            'national_id'       => ['required', 'string', 'size:14'],
   169	            'phone_primary'     => ['nullable', 'string', 'max:20'],
   170	        ];
   171	    }
   172	
   173	    public function customValidationMessages(): array
   174	    {
   175	        return [
   176	            'membership_number.required' => 'رقم العضوية مطلوب في الصف :attribute',
   177	            'name_ar.required'           => 'الاسم بالعربية مطلوب في الصف :attribute',
   178	            'national_id.required'       => 'الرقم القومي مطلوب في الصف :attribute',
   179	            'national_id.size'           => 'الرقم القومي يجب أن يكون 14 رقم في الصف :attribute',
   180	        ];
   181	    }
   182	
   183	    public function batchSize(): int
   184	    {
   185	        return 100;
   186	    }
   187	
   188	    public function chunkSize(): int
   189	    {
   190	        return 200;
   191	    }
   192	
   193	    public function getSuccessCount(): int
   194	    {
   195	        return $this->successCount;
   196	    }
   197	
   198	    public function getFailCount(): int
   199	    {
   200	        return $this->failCount;
   201	    }
   202	
   203	    public function getErrors(): array
   204	    {
   205	        return $this->errors;
   206	    }
   207	
   208	    // ─── Private Helpers ─────────────────────────────────────────────
   209	
   210	    private function resolveLookup(
   211	        Collection $map,
   212	        mixed $value,
   213	        string $label,
   214	        int $rowNumber,
   215	        bool $required = true,
   216	    ): ?int {
   217	        if (empty($value)) {
   218	            if ($required) {
   219	                throw new \RuntimeException("الصف {$rowNumber}: {$label} مطلوب ولكنه فارغ");
   220	            }
   221	            return null;
   222	        }
   223	
   224	        $trimmed = trim((string) $value);
   225	        $id = $map->get($trimmed);
   226	
   227	        if ($id === null && $required) {
   228	            throw new \RuntimeException(
   229	                "الصف {$rowNumber}: قيمة غير معروفة لـ {$label}: \"{$trimmed}\""
   230	            );
   231	        }
   232	
   233	        return $id;
   234	    }
   235	
   236	    private function resolveGender(Collection $row, ?array $nationalIdData): ?Gender
   237	    {
   238	        if (! empty($row['gender'])) {
   239	            $raw = trim((string) $row['gender']);
   240	            return match (true) {
   241	                in_array($raw, ['ذكر', 'male', 'M', 'm'])   => Gender::MALE,
   242	                in_array($raw, ['أنثى', 'female', 'F', 'f']) => Gender::FEMALE,
   243	                default => null,
   244	            };
   245	        }
   246	
   247	        if (! empty($nationalIdData['gender'])) {
   248	            return $nationalIdData['gender'];
   249	        }
   250	
   251	        return null;
   252	    }
   253	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [239]: app/Imports/MembersImport.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [240/494]: app/Models/BackupRecord.php
│ LANGUAGE: php | LINES: 44 | SIZE: 915 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Models;
     4	
     5	use App\Models\Traits\HasAuditTrail;
     6	use Illuminate\Database\Eloquent\Model;
     7	use Illuminate\Database\Eloquent\Relations\BelongsTo;
     8	
     9	class BackupRecord extends Model
    10	{
    11	    use HasAuditTrail;
    12	
    13	    protected $fillable = [
    14	        'backup_number',
    15	        'file_name',
    16	        'file_path',
    17	        'file_size',
    18	        'disk',
    19	        'status',
    20	        'type',
    21	        'started_at',
    22	        'completed_at',
    23	        'error_message',
    24	        'created_by',
    25	    ];
    26	
    27	    protected function casts(): array
    28	    {
    29	        return [
    30	            'file_size' => 'integer',
    31	            'started_at' => 'datetime',
    32	            'completed_at' => 'datetime',
    33	        ];
    34	    }
    35	
    36	    public function createdByUser(): BelongsTo
    37	    {
    38	        return $this->belongsTo(User::class, 'created_by');
    39	    }
    40	
    41	    public static function getAuditLabel(): string
    42	    {
    43	        return 'نسخة احتياطية';
    44	    }
    45	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [240]: app/Models/BackupRecord.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [241/494]: app/Models/BoardDecision.php
│ LANGUAGE: php | LINES: 79 | SIZE: 2010 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Models;
     4	
     5	use App\Enums\BoardDecisionType;
     6	use App\Enums\BoardDecisionResult;
     7	use Illuminate\Database\Eloquent\Factories\HasFactory;
     8	use Illuminate\Database\Eloquent\Model;
     9	use Illuminate\Database\Eloquent\Relations\BelongsTo;
    10	use Spatie\Activitylog\LogOptions;
    11	use Spatie\Activitylog\Traits\LogsActivity;
    12	
    13	class BoardDecision extends Model
    14	{
    15	    use HasFactory, LogsActivity;
    16	
    17	    protected $fillable = [
    18	        'board_offer_id',
    19	        'decision_type',
    20	        'result',
    21	        'decision_date',
    22	        'notes',
    23	        'decided_by',
    24	        'voting_for',
    25	        'voting_against',
    26	        'voting_abstain',
    27	        'conditions',
    28	        'effective_date',
    29	        'expiry_date',
    30	    ];
    31	
    32	    protected $casts = [
    33	        'decision_type' => BoardDecisionType::class,
    34	        'result' => BoardDecisionResult::class,
    35	        'decision_date' => 'date',
    36	        'effective_date' => 'date',
    37	        'expiry_date' => 'date',
    38	        'voting_for' => 'integer',
    39	        'voting_against' => 'integer',
    40	        'voting_abstain' => 'integer',
    41	    ];
    42	
    43	    public function getActivitylogOptions(): LogOptions
    44	    {
    45	        return LogOptions::defaults()
    46	            ->logAll()
    47	            ->logOnlyDirty()
    48	            ->useLogName('board_decisions');
    49	    }
    50	
    51	    public function boardOffer(): BelongsTo
    52	    {
    53	        return $this->belongsTo(BoardOffer::class);
    54	    }
    55	
    56	    public function decidedBy(): BelongsTo
    57	    {
    58	        return $this->belongsTo(User::class, 'decided_by');
    59	    }
    60	
    61	    public function getTotalVotesAttribute(): int
    62	    {
    63	        return ($this->voting_for ?? 0) + ($this->voting_against ?? 0) + ($this->voting_abstain ?? 0);
    64	    }
    65	
    66	    public function getApprovalPercentageAttribute(): ?float
    67	    {
    68	        $total = $this->total_votes;
    69	        if ($total === 0) {
    70	            return null;
    71	        }
    72	
    73	        return round(($this->voting_for / $total) * 100, 1);
    74	    }
    75	
    76	    public function isApproved(): bool
    77	    {
    78	        return $this->result === BoardDecisionResult::APPROVED;
    79	    }
    80	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [241]: app/Models/BoardDecision.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [242/494]: app/Models/BoardOffer.php
│ LANGUAGE: php | LINES: 124 | SIZE: 3099 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Models;
     4	
     5	use App\Enums\BoardOfferStatus;
     6	use Illuminate\Database\Eloquent\Factories\HasFactory;
     7	use Illuminate\Database\Eloquent\Model;
     8	use Illuminate\Database\Eloquent\Relations\BelongsTo;
     9	use Illuminate\Database\Eloquent\Relations\HasMany;
    10	use Illuminate\Database\Eloquent\Relations\MorphMany;
    11	use Spatie\Activitylog\LogOptions;
    12	use Spatie\Activitylog\Traits\LogsActivity;
    13	use Spatie\MediaLibrary\HasMedia;
    14	use Spatie\MediaLibrary\InteractsWithMedia;
    15	
    16	class BoardOffer extends Model implements HasMedia
    17	{
    18	    use HasFactory, LogsActivity, InteractsWithMedia;
    19	
    20	    protected $fillable = [
    21	        'offer_number',
    22	        'member_id',
    23	        'subject',
    24	        'description',
    25	        'status',
    26	        'category',
    27	        'priority',
    28	        'submitted_by',
    29	        'submitted_at',
    30	        'reviewed_by',
    31	        'reviewed_at',
    32	        'cancellation_reason',
    33	        'cancelled_at',
    34	        'cancelled_by',
    35	        'meeting_date',
    36	        'agenda_order',
    37	        'notes',
    38	    ];
    39	
    40	    protected $casts = [
    41	        'status' => BoardOfferStatus::class,
    42	        'submitted_at' => 'datetime',
    43	        'reviewed_at' => 'datetime',
    44	        'cancelled_at' => 'datetime',
    45	        'meeting_date' => 'date',
    46	        'agenda_order' => 'integer',
    47	    ];
    48	
    49	    public function getActivitylogOptions(): LogOptions
    50	    {
    51	        return LogOptions::defaults()
    52	            ->logAll()
    53	            ->logOnlyDirty()
    54	            ->useLogName('board_offers');
    55	    }
    56	
    57	    public function registerMediaCollections(): void
    58	    {
    59	        $this->addMediaCollection('attachments');
    60	    }
    61	
    62	    public function member(): BelongsTo
    63	    {
    64	        return $this->belongsTo(Member::class);
    65	    }
    66	
    67	    public function submittedBy(): BelongsTo
    68	    {
    69	        return $this->belongsTo(User::class, 'submitted_by');
    70	    }
    71	
    72	    public function reviewedBy(): BelongsTo
    73	    {
    74	        return $this->belongsTo(User::class, 'reviewed_by');
    75	    }
    76	
    77	    public function cancelledBy(): BelongsTo
    78	    {
    79	        return $this->belongsTo(User::class, 'cancelled_by');
    80	    }
    81	
    82	    public function decisions(): HasMany
    83	    {
    84	        return $this->hasMany(BoardDecision::class);
    85	    }
    86	
    87	    public function latestDecision()
    88	    {
    89	        return $this->hasOne(BoardDecision::class)->latestOfMany();
    90	    }
    91	
    92	    public function documents(): MorphMany
    93	    {
    94	        return $this->morphMany(Document::class, 'documentable');
    95	    }
    96	
    97	    public function scopePending($query)
    98	    {
    99	        return $query->where('status', BoardOfferStatus::PENDING);
   100	    }
   101	
   102	    public function scopeUnderReview($query)
   103	    {
   104	        return $query->where('status', BoardOfferStatus::UNDER_REVIEW);
   105	    }
   106	
   107	    public function scopeApproved($query)
   108	    {
   109	        return $query->where('status', BoardOfferStatus::APPROVED);
   110	    }
   111	
   112	    public function isPending(): bool
   113	    {
   114	        return $this->status === BoardOfferStatus::PENDING;
   115	    }
   116	
   117	    public function isFinalized(): bool
   118	    {
   119	        return in_array($this->status, [
   120	            BoardOfferStatus::APPROVED,
   121	            BoardOfferStatus::REJECTED,
   122	            BoardOfferStatus::CANCELLED,
   123	        ]);
   124	    }
   125	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [242]: app/Models/BoardOffer.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [243/494]: app/Models/Booking.php
│ LANGUAGE: php | LINES: 73 | SIZE: 1678 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Models;
     4	
     5	use App\Casts\MoneyCast;
     6	use App\Enums\BookingStatus;
     7	use App\Models\Traits\HasAuditTrail;
     8	use App\Models\Traits\HasMemberScope;
     9	use App\Models\Traits\HasNumberingSequence;
    10	use Illuminate\Database\Eloquent\Model;
    11	use Illuminate\Database\Eloquent\Relations\BelongsTo;
    12	
    13	class Booking extends Model
    14	{
    15	    use HasAuditTrail, HasMemberScope, HasNumberingSequence;
    16	
    17	    protected $fillable = [
    18	        'booking_number',
    19	        'member_id',
    20	        'dependent_id',
    21	        'facility_id',
    22	        'status',
    23	        'booking_date',
    24	        'start_time',
    25	        'end_time',
    26	        'duration_hours',
    27	        'amount',
    28	        'receipt_id',
    29	        'checked_in_at',
    30	        'checked_in_by',
    31	        'notes',
    32	        'cancelled_at',
    33	        'cancelled_by',
    34	        'cancellation_reason',
    35	        'created_by',
    36	    ];
    37	
    38	    protected function casts(): array
    39	    {
    40	        return [
    41	            'status' => BookingStatus::class,
    42	            'booking_date' => 'date',
    43	            'duration_hours' => 'decimal:2',
    44	            'amount' => MoneyCast::class,
    45	            'checked_in_at' => 'datetime',
    46	            'cancelled_at' => 'datetime',
    47	        ];
    48	    }
    49	
    50	    public function member(): BelongsTo
    51	    {
    52	        return $this->belongsTo(Member::class);
    53	    }
    54	
    55	    public function dependent(): BelongsTo
    56	    {
    57	        return $this->belongsTo(Dependent::class);
    58	    }
    59	
    60	    public function facility(): BelongsTo
    61	    {
    62	        return $this->belongsTo(Facility::class);
    63	    }
    64	
    65	    public function receipt(): BelongsTo
    66	    {
    67	        return $this->belongsTo(Receipt::class);
    68	    }
    69	
    70	    public static function getAuditLabel(): string
    71	    {
    72	        return 'حجز';
    73	    }
    74	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [243]: app/Models/Booking.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [244/494]: app/Models/Card.php
│ LANGUAGE: php | LINES: 83 | SIZE: 1920 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Models;
     4	
     5	use App\Enums\CardStatus;
     6	use App\Models\Traits\HasAuditTrail;
     7	use App\Models\Traits\HasMemberScope;
     8	use App\Models\Traits\HasNumberingSequence;
     9	use Illuminate\Database\Eloquent\Model;
    10	use Illuminate\Database\Eloquent\Relations\BelongsTo;
    11	use Illuminate\Database\Eloquent\SoftDeletes;
    12	
    13	class Card extends Model
    14	{
    15	    use SoftDeletes, HasAuditTrail, HasMemberScope, HasNumberingSequence;
    16	
    17	    protected $fillable = [
    18	        'card_number',
    19	        'member_id',
    20	        'dependent_id',
    21	        'card_type_id',
    22	        'status',
    23	        'issue_date',
    24	        'expiry_date',
    25	        'printed_at',
    26	        'issued_by',
    27	        'photo_path',
    28	        'replaced_by_card_id',
    29	        'replacement_reason',
    30	        'receipt_id',
    31	        'notes',
    32	        'created_by',
    33	    ];
    34	
    35	    protected function casts(): array
    36	    {
    37	        return [
    38	            'status' => CardStatus::class,
    39	            'issue_date' => 'date',
    40	            'expiry_date' => 'date',
    41	            'printed_at' => 'datetime',
    42	        ];
    43	    }
    44	
    45	    public function member(): BelongsTo
    46	    {
    47	        return $this->belongsTo(Member::class);
    48	    }
    49	
    50	    public function dependent(): BelongsTo
    51	    {
    52	        return $this->belongsTo(Dependent::class);
    53	    }
    54	
    55	    public function cardType(): BelongsTo
    56	    {
    57	        return $this->belongsTo(CardType::class);
    58	    }
    59	
    60	    public function issuedByUser(): BelongsTo
    61	    {
    62	        return $this->belongsTo(User::class, 'issued_by');
    63	    }
    64	
    65	    public function replacedByCard(): BelongsTo
    66	    {
    67	        return $this->belongsTo(Card::class, 'replaced_by_card_id');
    68	    }
    69	
    70	    public function receipt(): BelongsTo
    71	    {
    72	        return $this->belongsTo(Receipt::class);
    73	    }
    74	
    75	    public function getIsExpiredAttribute(): bool
    76	    {
    77	        return $this->expiry_date && $this->expiry_date->isPast();
    78	    }
    79	
    80	    public static function getAuditLabel(): string
    81	    {
    82	        return 'كارنيه';
    83	    }
    84	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [244]: app/Models/Card.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [245/494]: app/Models/CardType.php
│ LANGUAGE: php | LINES: 38 | SIZE: 833 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Models;
     4	
     5	use App\Models\Traits\HasActiveScope;
     6	use App\Models\Traits\HasBilingualName;
     7	use App\Models\Traits\HasDisplayOrder;
     8	use Illuminate\Database\Eloquent\Model;
     9	use Illuminate\Database\Eloquent\Relations\HasMany;
    10	
    11	class CardType extends Model
    12	{
    13	    use HasActiveScope, HasBilingualName, HasDisplayOrder;
    14	
    15	    public $timestamps = false;
    16	
    17	    protected $fillable = [
    18	        'name_ar',
    19	        'name_en',
    20	        'validity_months',
    21	        'template_path',
    22	        'is_active',
    23	        'display_order',
    24	    ];
    25	
    26	    protected function casts(): array
    27	    {
    28	        return [
    29	            'validity_months' => 'integer',
    30	            'is_active' => 'boolean',
    31	            'display_order' => 'integer',
    32	        ];
    33	    }
    34	
    35	    public function cards(): HasMany
    36	    {
    37	        return $this->hasMany(Card::class);
    38	    }
    39	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [245]: app/Models/CardType.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [246/494]: app/Models/CashRegister.php
│ LANGUAGE: php | LINES: 89 | SIZE: 2227 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Models;
     4	
     5	use App\Enums\CashRegisterStatus;
     6	use Illuminate\Database\Eloquent\Factories\HasFactory;
     7	use Illuminate\Database\Eloquent\Model;
     8	use Illuminate\Database\Eloquent\Relations\BelongsTo;
     9	use Illuminate\Database\Eloquent\Relations\HasMany;
    10	use Spatie\Activitylog\LogOptions;
    11	use Spatie\Activitylog\Traits\LogsActivity;
    12	
    13	class CashRegister extends Model
    14	{
    15	    use HasFactory, LogsActivity;
    16	
    17	    protected $fillable = [
    18	        'name',
    19	        'status',
    20	        'opened_by',
    21	        'opened_at',
    22	        'closed_by',
    23	        'closed_at',
    24	        'opening_balance',
    25	        'current_balance',
    26	        'closing_balance',
    27	        'counted_balance',
    28	        'difference',
    29	        'notes',
    30	        'closing_notes',
    31	        'suspension_reason',
    32	        'suspended_at',
    33	        'suspended_by',
    34	    ];
    35	
    36	    protected $casts = [
    37	        'status' => CashRegisterStatus::class,
    38	        'opened_at' => 'datetime',
    39	        'closed_at' => 'datetime',
    40	        'suspended_at' => 'datetime',
    41	        'opening_balance' => 'decimal:2',
    42	        'current_balance' => 'decimal:2',
    43	        'closing_balance' => 'decimal:2',
    44	        'counted_balance' => 'decimal:2',
    45	        'difference' => 'decimal:2',
    46	    ];
    47	
    48	    public function getActivitylogOptions(): LogOptions
    49	    {
    50	        return LogOptions::defaults()
    51	            ->logAll()
    52	            ->logOnlyDirty()
    53	            ->useLogName('cash_registers');
    54	    }
    55	
    56	    public function openedBy(): BelongsTo
    57	    {
    58	        return $this->belongsTo(User::class, 'opened_by');
    59	    }
    60	
    61	    public function closedBy(): BelongsTo
    62	    {
    63	        return $this->belongsTo(User::class, 'closed_by');
    64	    }
    65	
    66	    public function suspendedBy(): BelongsTo
    67	    {
    68	        return $this->belongsTo(User::class, 'suspended_by');
    69	    }
    70	
    71	    public function transactions(): HasMany
    72	    {
    73	        return $this->hasMany(CashRegisterTransaction::class);
    74	    }
    75	
    76	    public function scopeOpen($query)
    77	    {
    78	        return $query->where('status', CashRegisterStatus::OPEN);
    79	    }
    80	
    81	    public function scopeClosed($query)
    82	    {
    83	        return $query->where('status', CashRegisterStatus::CLOSED);
    84	    }
    85	
    86	    public function isOpen(): bool
    87	    {
    88	        return $this->status === CashRegisterStatus::OPEN;
    89	    }
    90	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [246]: app/Models/CashRegister.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [247/494]: app/Models/CashRegisterTransaction.php
│ LANGUAGE: php | LINES: 55 | SIZE: 1182 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Models;
     4	
     5	use Illuminate\Database\Eloquent\Factories\HasFactory;
     6	use Illuminate\Database\Eloquent\Model;
     7	use Illuminate\Database\Eloquent\Relations\BelongsTo;
     8	use Illuminate\Database\Eloquent\Relations\MorphTo;
     9	
    10	class CashRegisterTransaction extends Model
    11	{
    12	    use HasFactory;
    13	
    14	    protected $fillable = [
    15	        'cash_register_id',
    16	        'type',
    17	        'amount',
    18	        'balance_before',
    19	        'balance_after',
    20	        'description',
    21	        'reference_type',
    22	        'reference_id',
    23	        'created_by',
    24	    ];
    25	
    26	    protected $casts = [
    27	        'amount' => 'decimal:2',
    28	        'balance_before' => 'decimal:2',
    29	        'balance_after' => 'decimal:2',
    30	    ];
    31	
    32	    public function cashRegister(): BelongsTo
    33	    {
    34	        return $this->belongsTo(CashRegister::class);
    35	    }
    36	
    37	    public function reference(): MorphTo
    38	    {
    39	        return $this->morphTo();
    40	    }
    41	
    42	    public function createdBy(): BelongsTo
    43	    {
    44	        return $this->belongsTo(User::class, 'created_by');
    45	    }
    46	
    47	    public function isIncome(): bool
    48	    {
    49	        return $this->type === 'in';
    50	    }
    51	
    52	    public function isExpense(): bool
    53	    {
    54	        return $this->type === 'out';
    55	    }
    56	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [247]: app/Models/CashRegisterTransaction.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [248/494]: app/Models/Dependent.php
│ LANGUAGE: php | LINES: 91 | SIZE: 2204 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Models;
     4	
     5	use App\Enums\DependentStatus;
     6	use App\Enums\Gender;
     7	use App\Models\Traits\HasAuditTrail;
     8	use App\Models\Traits\HasMemberScope;
     9	use Illuminate\Database\Eloquent\Model;
    10	use Illuminate\Database\Eloquent\Relations\BelongsTo;
    11	use Illuminate\Database\Eloquent\Relations\HasMany;
    12	use Illuminate\Database\Eloquent\SoftDeletes;
    13	use Spatie\MediaLibrary\HasMedia;
    14	use Spatie\MediaLibrary\InteractsWithMedia;
    15	
    16	class Dependent extends Model implements HasMedia
    17	{
    18	    use SoftDeletes, HasAuditTrail, HasMemberScope, InteractsWithMedia;
    19	
    20	    protected $fillable = [
    21	        'member_id',
    22	        'dependent_membership_number',
    23	        'relationship_type_id',
    24	        'full_name_ar',
    25	        'full_name_en',
    26	        'national_id',
    27	        'gender',
    28	        'date_of_birth',
    29	        'phone',
    30	        'email',
    31	        'status',
    32	        'status_changed_at',
    33	        'status_changed_by',
    34	        'profile_photo_path',
    35	        'notes',
    36	        'created_by',
    37	    ];
    38	
    39	    protected function casts(): array
    40	    {
    41	        return [
    42	            'gender' => Gender::class,
    43	            'date_of_birth' => 'date',
    44	            'status' => DependentStatus::class,
    45	            'status_changed_at' => 'datetime',
    46	        ];
    47	    }
    48	
    49	    public function member(): BelongsTo
    50	    {
    51	        return $this->belongsTo(Member::class);
    52	    }
    53	
    54	    public function relationshipType(): BelongsTo
    55	    {
    56	        return $this->belongsTo(RelationshipType::class);
    57	    }
    58	
    59	    public function cards(): HasMany
    60	    {
    61	        return $this->hasMany(Card::class);
    62	    }
    63	
    64	    public function documents(): HasMany
    65	    {
    66	        return $this->hasMany(Document::class);
    67	    }
    68	
    69	    public function receipts(): HasMany
    70	    {
    71	        return $this->hasMany(Receipt::class);
    72	    }
    73	
    74	    public function getAgeAttribute(): ?int
    75	    {
    76	        return $this->date_of_birth?->age;
    77	    }
    78	
    79	    public function registerMediaCollections(): void
    80	    {
    81	        $this->addMediaCollection('profile_photo')
    82	            ->singleFile()
    83	            ->acceptsMimeTypes(['image/jpeg', 'image/png', 'image/webp']);
    84	
    85	        $this->addMediaCollection('documents');
    86	    }
    87	
    88	    public static function getAuditLabel(): string
    89	    {
    90	        return 'تابع';
    91	    }
    92	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [248]: app/Models/Dependent.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [249/494]: app/Models/Document.php
│ LANGUAGE: php | LINES: 75 | SIZE: 1661 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Models;
     4	
     5	use App\Models\Traits\HasAuditTrail;
     6	use App\Models\Traits\HasMemberScope;
     7	use Illuminate\Database\Eloquent\Model;
     8	use Illuminate\Database\Eloquent\Relations\BelongsTo;
     9	use Illuminate\Database\Eloquent\SoftDeletes;
    10	
    11	class Document extends Model
    12	{
    13	    use SoftDeletes, HasAuditTrail, HasMemberScope;
    14	
    15	    protected $fillable = [
    16	        'member_id',
    17	        'dependent_id',
    18	        'document_type_id',
    19	        'documentable_type',
    20	        'documentable_id',
    21	        'file_path',
    22	        'original_filename',
    23	        'file_size',
    24	        'mime_type',
    25	        'is_verified',
    26	        'verified_at',
    27	        'verified_by',
    28	        'rejection_reason',
    29	        'notes',
    30	        'uploaded_by',
    31	    ];
    32	
    33	    protected function casts(): array
    34	    {
    35	        return [
    36	            'file_size' => 'integer',
    37	            'is_verified' => 'boolean',
    38	            'verified_at' => 'datetime',
    39	        ];
    40	    }
    41	
    42	    public function member(): BelongsTo
    43	    {
    44	        return $this->belongsTo(Member::class);
    45	    }
    46	
    47	    public function dependent(): BelongsTo
    48	    {
    49	        return $this->belongsTo(Dependent::class);
    50	    }
    51	
    52	    public function documentType(): BelongsTo
    53	    {
    54	        return $this->belongsTo(DocumentType::class);
    55	    }
    56	
    57	    public function verifiedByUser(): BelongsTo
    58	    {
    59	        return $this->belongsTo(User::class, 'verified_by');
    60	    }
    61	
    62	    public function uploadedByUser(): BelongsTo
    63	    {
    64	        return $this->belongsTo(User::class, 'uploaded_by');
    65	    }
    66	
    67	    public function documentable()
    68	    {
    69	        return $this->morphTo();
    70	    }
    71	
    72	    public static function getAuditLabel(): string
    73	    {
    74	        return 'مستند';
    75	    }
    76	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [249]: app/Models/Document.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [250/494]: app/Models/DocumentType.php
│ LANGUAGE: php | LINES: 45 | SIZE: 1062 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Models;
     4	
     5	use App\Models\Traits\HasActiveScope;
     6	use App\Models\Traits\HasBilingualName;
     7	use App\Models\Traits\HasDisplayOrder;
     8	use Illuminate\Database\Eloquent\Model;
     9	use Illuminate\Database\Eloquent\Relations\HasMany;
    10	
    11	class DocumentType extends Model
    12	{
    13	    use HasActiveScope, HasBilingualName, HasDisplayOrder;
    14	
    15	    public $timestamps = false;
    16	
    17	    protected $fillable = [
    18	        'type_key',
    19	        'name_ar',
    20	        'name_en',
    21	        'is_required',
    22	        'required_for_stage',
    23	        'allowed_extensions',
    24	        'max_file_size_kb',
    25	        'description_ar',
    26	        'description_en',
    27	        'is_active',
    28	        'display_order',
    29	    ];
    30	
    31	    protected function casts(): array
    32	    {
    33	        return [
    34	            'is_required' => 'boolean',
    35	            'allowed_extensions' => 'array',
    36	            'max_file_size_kb' => 'integer',
    37	            'is_active' => 'boolean',
    38	            'display_order' => 'integer',
    39	        ];
    40	    }
    41	
    42	    public function documents(): HasMany
    43	    {
    44	        return $this->hasMany(Document::class);
    45	    }
    46	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [250]: app/Models/DocumentType.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [251/494]: app/Models/EducationalQualification.php
│ LANGUAGE: php | LINES: 29 | SIZE: 607 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Models;
     4	
     5	use App\Models\Traits\HasActiveScope;
     6	use App\Models\Traits\HasBilingualName;
     7	use App\Models\Traits\HasDisplayOrder;
     8	use Illuminate\Database\Eloquent\Model;
     9	
    10	class EducationalQualification extends Model
    11	{
    12	    use HasActiveScope, HasBilingualName, HasDisplayOrder;
    13	
    14	    public $timestamps = false;
    15	
    16	    protected $fillable = [
    17	        'name_ar',
    18	        'name_en',
    19	        'is_active',
    20	        'display_order',
    21	    ];
    22	
    23	    protected function casts(): array
    24	    {
    25	        return [
    26	            'is_active' => 'boolean',
    27	            'display_order' => 'integer',
    28	        ];
    29	    }
    30	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [251]: app/Models/EducationalQualification.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [252/494]: app/Models/ExitReason.php
│ LANGUAGE: php | LINES: 30 | SIZE: 615 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Models;
     4	
     5	use App\Models\Traits\HasActiveScope;
     6	use App\Models\Traits\HasBilingualName;
     7	use App\Models\Traits\HasDisplayOrder;
     8	use Illuminate\Database\Eloquent\Model;
     9	
    10	class ExitReason extends Model
    11	{
    12	    use HasActiveScope, HasBilingualName, HasDisplayOrder;
    13	
    14	    public $timestamps = false;
    15	
    16	    protected $fillable = [
    17	        'reason_key',
    18	        'name_ar',
    19	        'name_en',
    20	        'display_order',
    21	        'is_active',
    22	    ];
    23	
    24	    protected function casts(): array
    25	    {
    26	        return [
    27	            'is_active' => 'boolean',
    28	            'display_order' => 'integer',
    29	        ];
    30	    }
    31	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [252]: app/Models/ExitReason.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [253/494]: app/Models/Facility.php
│ LANGUAGE: php | LINES: 49 | SIZE: 1263 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Models;
     4	
     5	use App\Casts\MoneyCast;
     6	use App\Enums\GenderRestriction;
     7	use App\Models\Traits\HasActiveScope;
     8	use App\Models\Traits\HasBilingualName;
     9	use App\Models\Traits\HasDisplayOrder;
    10	use Illuminate\Database\Eloquent\Model;
    11	use Illuminate\Database\Eloquent\Relations\HasMany;
    12	
    13	class Facility extends Model
    14	{
    15	    use HasActiveScope, HasBilingualName, HasDisplayOrder;
    16	
    17	    protected $fillable = [
    18	        'name_ar',
    19	        'name_en',
    20	        'description_ar',
    21	        'description_en',
    22	        'capacity',
    23	        'hourly_rate',
    24	        'daily_rate',
    25	        'gender_restriction',
    26	        'requires_booking',
    27	        'requires_subscription',
    28	        'is_active',
    29	        'display_order',
    30	    ];
    31	
    32	    protected function casts(): array
    33	    {
    34	        return [
    35	            'capacity' => 'integer',
    36	            'hourly_rate' => MoneyCast::class,
    37	            'daily_rate' => MoneyCast::class,
    38	            'gender_restriction' => GenderRestriction::class,
    39	            'requires_booking' => 'boolean',
    40	            'requires_subscription' => 'boolean',
    41	            'is_active' => 'boolean',
    42	            'display_order' => 'integer',
    43	        ];
    44	    }
    45	
    46	    public function bookings(): HasMany
    47	    {
    48	        return $this->hasMany(Booking::class);
    49	    }
    50	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [253]: app/Models/Facility.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [254/494]: app/Models/FeeItem.php
│ LANGUAGE: php | LINES: 42 | SIZE: 905 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Models;
     4	
     5	use App\Enums\FeeCategory;
     6	use App\Models\Traits\HasActiveScope;
     7	use App\Models\Traits\HasAuditTrail;
     8	use App\Models\Traits\HasBilingualName;
     9	use Illuminate\Database\Eloquent\Model;
    10	use Illuminate\Database\Eloquent\Relations\HasMany;
    11	
    12	class FeeItem extends Model
    13	{
    14	    use HasActiveScope, HasAuditTrail, HasBilingualName;
    15	
    16	    protected $fillable = [
    17	        'fee_key',
    18	        'category',
    19	        'name_ar',
    20	        'name_en',
    21	        'description_ar',
    22	        'description_en',
    23	        'is_active',
    24	    ];
    25	
    26	    protected function casts(): array
    27	    {
    28	        return [
    29	            'category' => FeeCategory::class,
    30	            'is_active' => 'boolean',
    31	        ];
    32	    }
    33	
    34	    public function schedules(): HasMany
    35	    {
    36	        return $this->hasMany(FeeSchedule::class);
    37	    }
    38	
    39	    public static function getAuditLabel(): string
    40	    {
    41	        return 'بند رسوم';
    42	    }
    43	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [254]: app/Models/FeeItem.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [255/494]: app/Models/FeeSchedule.php
│ LANGUAGE: php | LINES: 57 | SIZE: 1289 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Models;
     4	
     5	use App\Casts\MoneyCast;
     6	use App\Models\Traits\HasAuditTrail;
     7	use Illuminate\Database\Eloquent\Model;
     8	use Illuminate\Database\Eloquent\Relations\BelongsTo;
     9	
    10	class FeeSchedule extends Model
    11	{
    12	    use HasAuditTrail;
    13	
    14	    protected $fillable = [
    15	        'fee_item_id',
    16	        'membership_type_id',
    17	        'fiscal_year_id',
    18	        'amount',
    19	        'late_fee_amount',
    20	        'late_fee_after_days',
    21	        'effective_from',
    22	        'effective_to',
    23	        'is_active',
    24	        'notes',
    25	    ];
    26	
    27	    protected function casts(): array
    28	    {
    29	        return [
    30	            'amount' => MoneyCast::class,
    31	            'late_fee_amount' => MoneyCast::class,
    32	            'late_fee_after_days' => 'integer',
    33	            'effective_from' => 'date',
    34	            'effective_to' => 'date',
    35	            'is_active' => 'boolean',
    36	        ];
    37	    }
    38	
    39	    public function feeItem(): BelongsTo
    40	    {
    41	        return $this->belongsTo(FeeItem::class);
    42	    }
    43	
    44	    public function membershipType(): BelongsTo
    45	    {
    46	        return $this->belongsTo(MembershipType::class);
    47	    }
    48	
    49	    public function fiscalYear(): BelongsTo
    50	    {
    51	        return $this->belongsTo(FiscalYear::class);
    52	    }
    53	
    54	    public static function getAuditLabel(): string
    55	    {
    56	        return 'جدول رسوم';
    57	    }
    58	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [255]: app/Models/FeeSchedule.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [256/494]: app/Models/FeeStructure.php
│ LANGUAGE: php | LINES: 60 | SIZE: 1379 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Models;
     4	
     5	use App\Models\Traits\Archivable;
     6	use Illuminate\Database\Eloquent\Model;
     7	use Illuminate\Database\Eloquent\Relations\BelongsTo;
     8	
     9	class FeeStructure extends Model
    10	{
    11	    use Archivable;
    12	
    13	    protected $fillable = [
    14	        'fee_category',
    15	        'membership_type_id',
    16	        'name_ar',
    17	        'name_en',
    18	        'amount',
    19	        'age_from',
    20	        'age_to',
    21	        'effective_from',
    22	        'effective_to',
    23	        'is_active',
    24	        'notes_ar',
    25	        'notes_en',
    26	        'created_by',
    27	        'is_archived',
    28	    ];
    29	
    30	    protected $casts = [
    31	        'amount' => 'decimal:2',
    32	        'effective_from' => 'date',
    33	        'effective_to' => 'date',
    34	        'is_active' => 'boolean',
    35	        'is_archived' => 'boolean',
    36	    ];
    37	
    38	    public function membershipType(): BelongsTo
    39	    {
    40	        return $this->belongsTo(MembershipType::class);
    41	    }
    42	
    43	    public function createdBy(): BelongsTo
    44	    {
    45	        return $this->belongsTo(User::class, 'created_by');
    46	    }
    47	
    48	    public function scopeActive($query)
    49	    {
    50	        return $query->where('is_active', true);
    51	    }
    52	
    53	    public function scopeCurrentlyEffective($query)
    54	    {
    55	        return $query->where('effective_from', '<=', now())
    56	            ->where(function ($q) {
    57	                $q->whereNull('effective_to')
    58	                    ->orWhere('effective_to', '>=', now());
    59	            });
    60	    }
    61	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [256]: app/Models/FeeStructure.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [257/494]: app/Models/FiscalYear.php
│ LANGUAGE: php | LINES: 53 | SIZE: 1122 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Models;
     4	
     5	use App\Models\Traits\HasAuditTrail;
     6	use Illuminate\Database\Eloquent\Model;
     7	use Illuminate\Database\Eloquent\Relations\HasMany;
     8	
     9	class FiscalYear extends Model
    10	{
    11	    use HasAuditTrail;
    12	
    13	    protected $fillable = [
    14	        'year_label',
    15	        'start_date',
    16	        'end_date',
    17	        'is_current',
    18	        'is_closed',
    19	        'closed_at',
    20	        'closed_by',
    21	        'notes',
    22	    ];
    23	
    24	    protected function casts(): array
    25	    {
    26	        return [
    27	            'start_date' => 'date',
    28	            'end_date' => 'date',
    29	            'is_current' => 'boolean',
    30	            'is_closed' => 'boolean',
    31	            'closed_at' => 'datetime',
    32	        ];
    33	    }
    34	
    35	    public function receipts(): HasMany
    36	    {
    37	        return $this->hasMany(Receipt::class);
    38	    }
    39	
    40	    public function numberingSequences(): HasMany
    41	    {
    42	        return $this->hasMany(NumberingSequence::class);
    43	    }
    44	
    45	    public static function current(): ?static
    46	    {
    47	        return static::where('is_current', true)->first();
    48	    }
    49	
    50	    public static function getAuditLabel(): string
    51	    {
    52	        return 'سنة مالية';
    53	    }
    54	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [257]: app/Models/FiscalYear.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [258/494]: app/Models/Governorate.php
│ LANGUAGE: php | LINES: 30 | SIZE: 622 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Models;
     4	
     5	use App\Models\Traits\HasActiveScope;
     6	use App\Models\Traits\HasBilingualName;
     7	use App\Models\Traits\HasDisplayOrder;
     8	use Illuminate\Database\Eloquent\Model;
     9	
    10	class Governorate extends Model
    11	{
    12	    use HasActiveScope, HasBilingualName, HasDisplayOrder;
    13	
    14	    public $timestamps = false;
    15	
    16	    protected $fillable = [
    17	        'name_ar',
    18	        'name_en',
    19	        'national_id_code',
    20	        'is_active',
    21	        'display_order',
    22	    ];
    23	
    24	    protected function casts(): array
    25	    {
    26	        return [
    27	            'is_active' => 'boolean',
    28	            'display_order' => 'integer',
    29	        ];
    30	    }
    31	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [258]: app/Models/Governorate.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [259/494]: app/Models/ImportJob.php
│ LANGUAGE: php | LINES: 50 | SIZE: 1133 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Models;
     4	
     5	use App\Models\Traits\HasAuditTrail;
     6	use Illuminate\Database\Eloquent\Model;
     7	use Illuminate\Database\Eloquent\Relations\BelongsTo;
     8	
     9	class ImportJob extends Model
    10	{
    11	    use HasAuditTrail;
    12	
    13	    protected $fillable = [
    14	        'import_number',
    15	        'type',
    16	        'file_name',
    17	        'file_path',
    18	        'status',
    19	        'total_rows',
    20	        'processed_rows',
    21	        'successful_rows',
    22	        'failed_rows',
    23	        'error_log',
    24	        'started_at',
    25	        'completed_at',
    26	        'created_by',
    27	    ];
    28	
    29	    protected function casts(): array
    30	    {
    31	        return [
    32	            'total_rows' => 'integer',
    33	            'processed_rows' => 'integer',
    34	            'successful_rows' => 'integer',
    35	            'failed_rows' => 'integer',
    36	            'error_log' => 'array',
    37	            'started_at' => 'datetime',
    38	            'completed_at' => 'datetime',
    39	        ];
    40	    }
    41	
    42	    public function createdByUser(): BelongsTo
    43	    {
    44	        return $this->belongsTo(User::class, 'created_by');
    45	    }
    46	
    47	    public static function getAuditLabel(): string
    48	    {
    49	        return 'وظيفة استيراد';
    50	    }
    51	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [259]: app/Models/ImportJob.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [260/494]: app/Models/Installment.php
│ LANGUAGE: php | LINES: 48 | SIZE: 1086 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Models;
     4	
     5	use Illuminate\Database\Eloquent\Model;
     6	use Illuminate\Database\Eloquent\Relations\BelongsTo;
     7	use Illuminate\Database\Eloquent\Relations\HasMany;
     8	
     9	class Installment extends Model
    10	{
    11	    protected $fillable = [
    12	        'installment_plan_id',
    13	        'installment_number',
    14	        'amount',
    15	        'amount_paid',
    16	        'amount_remaining',
    17	        'due_date',
    18	        'paid_at',
    19	        'status',
    20	    ];
    21	
    22	    protected $casts = [
    23	        'due_date' => 'date',
    24	        'paid_at' => 'datetime',
    25	        'amount' => 'decimal:2',
    26	        'amount_paid' => 'decimal:2',
    27	        'amount_remaining' => 'decimal:2',
    28	    ];
    29	
    30	    public function plan(): BelongsTo
    31	    {
    32	        return $this->belongsTo(InstallmentPlan::class, 'installment_plan_id');
    33	    }
    34	
    35	    public function receipts(): HasMany
    36	    {
    37	        return $this->hasMany(Receipt::class);
    38	    }
    39	
    40	    public function isOverdue(): bool
    41	    {
    42	        return $this->due_date < now() && $this->amount_remaining > 0;
    43	    }
    44	
    45	    public function isPaid(): bool
    46	    {
    47	        return $this->status === 'paid';
    48	    }
    49	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [260]: app/Models/Installment.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [261/494]: app/Models/InstallmentPlan.php
│ LANGUAGE: php | LINES: 101 | SIZE: 2615 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Models;
     4	
     5	use App\Models\Traits\Archivable;
     6	use Illuminate\Database\Eloquent\Model;
     7	use Illuminate\Database\Eloquent\Relations\BelongsTo;
     8	use Illuminate\Database\Eloquent\Relations\HasMany;
     9	use Spatie\Activitylog\Traits\LogsActivity;
    10	use Spatie\Activitylog\LogOptions;
    11	
    12	class InstallmentPlan extends Model
    13	{
    14	    use Archivable, LogsActivity;
    15	
    16	    protected $fillable = [
    17	        'member_id',
    18	        'subscription_id',
    19	        'total_amount',
    20	        'down_payment',
    21	        'installment_amount',
    22	        'number_of_installments',
    23	        'interval_days',
    24	        'start_date',
    25	        'status',
    26	        'approved_by',
    27	        'cancellation_reason',
    28	        'cancelled_by',
    29	        'cancelled_at',
    30	        'notes_ar',
    31	        'notes_en',
    32	        'created_by',
    33	        'is_archived',
    34	    ];
    35	
    36	    protected $casts = [
    37	        'start_date' => 'date',
    38	        'cancelled_at' => 'datetime',
    39	        'total_amount' => 'decimal:2',
    40	        'down_payment' => 'decimal:2',
    41	        'installment_amount' => 'decimal:2',
    42	        'is_archived' => 'boolean',
    43	    ];
    44	
    45	    public function getActivitylogOptions(): LogOptions
    46	    {
    47	        return LogOptions::defaults()
    48	            ->logAll()
    49	            ->logOnlyDirty()
    50	            ->useLogName('installment_plans');
    51	    }
    52	
    53	    public function member(): BelongsTo
    54	    {
    55	        return $this->belongsTo(Member::class);
    56	    }
    57	
    58	    public function subscription(): BelongsTo
    59	    {
    60	        return $this->belongsTo(Subscription::class);
    61	    }
    62	
    63	    public function installments(): HasMany
    64	    {
    65	        return $this->hasMany(Installment::class)->orderBy('installment_number');
    66	    }
    67	
    68	    public function approvedBy(): BelongsTo
    69	    {
    70	        return $this->belongsTo(User::class, 'approved_by');
    71	    }
    72	
    73	    public function createdBy(): BelongsTo
    74	    {
    75	        return $this->belongsTo(User::class, 'created_by');
    76	    }
    77	
    78	    // Accessors
    79	    public function getTotalPaidAttribute(): float
    80	    {
    81	        return (float) $this->installments->sum('amount_paid') + $this->down_payment;
    82	    }
    83	
    84	    public function getTotalRemainingAttribute(): float
    85	    {
    86	        return max(0, $this->total_amount - $this->total_paid);
    87	    }
    88	
    89	    public function getNextDueInstallmentAttribute(): ?Installment
    90	    {
    91	        return $this->installments
    92	            ->whereIn('status', ['pending', 'partially_paid', 'overdue'])
    93	            ->sortBy('installment_number')
    94	            ->first();
    95	    }
    96	
    97	    public function getProgressPercentAttribute(): float
    98	    {
    99	        if ($this->total_amount <= 0) return 100;
   100	        return round(($this->total_paid / $this->total_amount) * 100, 1);
   101	    }
   102	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [261]: app/Models/InstallmentPlan.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [262/494]: app/Models/Interview.php
│ LANGUAGE: php | LINES: 63 | SIZE: 1488 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Models;
     4	
     5	use App\Enums\InterviewResult;
     6	use App\Enums\InterviewStatus;
     7	use App\Models\Traits\HasAuditTrail;
     8	use App\Models\Traits\HasMemberScope;
     9	use Illuminate\Database\Eloquent\Model;
    10	use Illuminate\Database\Eloquent\Relations\BelongsTo;
    11	
    12	class Interview extends Model
    13	{
    14	    use HasAuditTrail, HasMemberScope;
    15	
    16	    protected $fillable = [
    17	        'member_id',
    18	        'status',
    19	        'result',
    20	        'scheduled_date',
    21	        'scheduled_time',
    22	        'actual_date',
    23	        'location',
    24	        'interviewer_id',
    25	        'panel_members',
    26	        'evaluation_score',
    27	        'notes',
    28	        'internal_notes',
    29	        'rescheduled_from_id',
    30	        'created_by',
    31	    ];
    32	
    33	    protected function casts(): array
    34	    {
    35	        return [
    36	            'status' => InterviewStatus::class,
    37	            'result' => InterviewResult::class,
    38	            'scheduled_date' => 'date',
    39	            'actual_date' => 'date',
    40	            'panel_members' => 'array',
    41	            'evaluation_score' => 'integer',
    42	        ];
    43	    }
    44	
    45	    public function member(): BelongsTo
    46	    {
    47	        return $this->belongsTo(Member::class);
    48	    }
    49	
    50	    public function interviewer(): BelongsTo
    51	    {
    52	        return $this->belongsTo(User::class, 'interviewer_id');
    53	    }
    54	
    55	    public function rescheduledFrom(): BelongsTo
    56	    {
    57	        return $this->belongsTo(Interview::class, 'rescheduled_from_id');
    58	    }
    59	
    60	    public static function getAuditLabel(): string
    61	    {
    62	        return 'مقابلة';
    63	    }
    64	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [262]: app/Models/Interview.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [263/494]: app/Models/MaritalStatus.php
│ LANGUAGE: php | LINES: 30 | SIZE: 639 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Models;
     4	
     5	use App\Models\Traits\HasActiveScope;
     6	use App\Models\Traits\HasBilingualName;
     7	use App\Models\Traits\HasDisplayOrder;
     8	use Illuminate\Database\Eloquent\Model;
     9	
    10	class MaritalStatus extends Model
    11	{
    12	    use HasActiveScope, HasBilingualName, HasDisplayOrder;
    13	
    14	    public $timestamps = false;
    15	    protected $table = 'marital_statuses';
    16	
    17	    protected $fillable = [
    18	        'name_ar',
    19	        'name_en',
    20	        'is_active',
    21	        'display_order',
    22	    ];
    23	
    24	    protected function casts(): array
    25	    {
    26	        return [
    27	            'is_active' => 'boolean',
    28	            'display_order' => 'integer',
    29	        ];
    30	    }
    31	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [263]: app/Models/MaritalStatus.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [264/494]: app/Models/Member.php
│ LANGUAGE: php | LINES: 335 | SIZE: 9432 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Models;
     4	
     5	use App\Casts\MoneyCast;
     6	use App\Models\Traits\Archivable;
     7	use App\Models\Traits\HasAuditTrail;
     8	use App\Models\Traits\HasMemberScope;
     9	use App\Models\Traits\HasNumberingSequence;
    10	use Illuminate\Database\Eloquent\Factories\HasFactory;
    11	use Illuminate\Database\Eloquent\Model;
    12	use Illuminate\Database\Eloquent\Relations\BelongsTo;
    13	use Illuminate\Database\Eloquent\Relations\HasMany;
    14	use Illuminate\Database\Eloquent\Relations\HasOne;
    15	use Illuminate\Database\Eloquent\SoftDeletes;
    16	use Spatie\MediaLibrary\HasMedia;
    17	use Spatie\MediaLibrary\InteractsWithMedia;
    18	
    19	class Member extends Model implements HasMedia
    20	{
    21	    use HasFactory, SoftDeletes, Archivable, HasAuditTrail, HasNumberingSequence, InteractsWithMedia;
    22	
    23	    protected $fillable = [
    24	        'membership_number_old',
    25	        'membership_number',
    26	        'membership_type_id',
    27	        'status_id',
    28	        'full_name_ar',
    29	        'full_name_en',
    30	        'national_id',
    31	        'national_id_verified',
    32	        'passport_number',
    33	        'nationality_id',
    34	        'religion_id',
    35	        'marital_status_id',
    36	        'gender',
    37	        'date_of_birth',
    38	        'birth_governorate_id',
    39	        'job_title',
    40	        'employer_name',
    41	        'educational_qualification_id',
    42	        'phone_primary',
    43	        'phone_secondary',
    44	        'email',
    45	        'address_governorate_id',
    46	        'address_city',
    47	        'address_street',
    48	        'address_postal_code',
    49	        'full_address_ar',
    50	        'full_address_en',
    51	        'profile_photo_path',
    52	        'application_form_number',
    53	        'application_form_date',
    54	        'application_date',
    55	        'membership_start_date',
    56	        'membership_end_date',
    57	        'activation_date',
    58	        'board_decision_reference',
    59	        'board_decision_date',
    60	        'board_decision_notes',
    61	        'sponsor_member_1_id',
    62	        'sponsor_member_2_id',
    63	        'referral_source',
    64	        'last_payment_date',
    65	        'last_payment_receipt_id',
    66	        'last_payment_year',
    67	        'last_renewal_date',
    68	        'paid_through_year',
    69	        'total_paid_amount',
    70	        'outstanding_balance',
    71	        'blood_type',
    72	        'emergency_contact_name',
    73	        'emergency_contact_phone',
    74	        'emergency_contact_relation',
    75	        'notes',
    76	        'internal_notes',
    77	        'created_by',
    78	        'updated_by',
    79	        'archived_at',
    80	        'archived_by',
    81	    ];
    82	
    83	    protected function casts(): array
    84	    {
    85	        return [
    86	            'national_id_verified' => 'boolean',
    87	            'date_of_birth' => 'date',
    88	            'application_form_date' => 'date',
    89	            'application_date' => 'datetime',
    90	            'membership_start_date' => 'date',
    91	            'membership_end_date' => 'date',
    92	            'activation_date' => 'date',
    93	            'board_decision_date' => 'date',
    94	            'last_payment_date' => 'date',
    95	            'last_renewal_date' => 'date',
    96	            'total_paid_amount' => MoneyCast::class,
    97	            'outstanding_balance' => MoneyCast::class,
    98	            'archived_at' => 'datetime',
    99	        ];
   100	    }
   101	
   102	    // ─── Relationships ───────────────────────────────────────────
   103	
   104	    public function membershipType(): BelongsTo
   105	    {
   106	        return $this->belongsTo(MembershipType::class);
   107	    }
   108	
   109	    public function status(): BelongsTo
   110	    {
   111	        return $this->belongsTo(MembershipStatus::class, 'status_id');
   112	    }
   113	
   114	    public function nationality(): BelongsTo
   115	    {
   116	        return $this->belongsTo(Nationality::class);
   117	    }
   118	
   119	    public function religion(): BelongsTo
   120	    {
   121	        return $this->belongsTo(Religion::class);
   122	    }
   123	
   124	    public function maritalStatus(): BelongsTo
   125	    {
   126	        return $this->belongsTo(MaritalStatus::class);
   127	    }
   128	
   129	    public function birthGovernorate(): BelongsTo
   130	    {
   131	        return $this->belongsTo(Governorate::class, 'birth_governorate_id');
   132	    }
   133	
   134	    public function addressGovernorate(): BelongsTo
   135	    {
   136	        return $this->belongsTo(Governorate::class, 'address_governorate_id');
   137	    }
   138	
   139	    public function educationalQualification(): BelongsTo
   140	    {
   141	        return $this->belongsTo(EducationalQualification::class);
   142	    }
   143	
   144	    public function sponsor1(): BelongsTo
   145	    {
   146	        return $this->belongsTo(Member::class, 'sponsor_member_1_id');
   147	    }
   148	
   149	    public function sponsor2(): BelongsTo
   150	    {
   151	        return $this->belongsTo(Member::class, 'sponsor_member_2_id');
   152	    }
   153	
   154	    public function sponsoredMembers1(): HasMany
   155	    {
   156	        return $this->hasMany(Member::class, 'sponsor_member_1_id');
   157	    }
   158	
   159	    public function sponsoredMembers2(): HasMany
   160	    {
   161	        return $this->hasMany(Member::class, 'sponsor_member_2_id');
   162	    }
   163	
   164	    public function dependents(): HasMany
   165	    {
   166	        return $this->hasMany(Dependent::class);
   167	    }
   168	
   169	    public function receipts(): HasMany
   170	    {
   171	        return $this->hasMany(Receipt::class);
   172	    }
   173	
   174	    public function violations(): HasMany
   175	    {
   176	        return $this->hasMany(Violation::class);
   177	    }
   178	
   179	    public function penalties(): HasMany
   180	    {
   181	        return $this->hasMany(Penalty::class);
   182	    }
   183	
   184	    public function suspensions(): HasMany
   185	    {
   186	        return $this->hasMany(MemberSuspension::class);
   187	    }
   188	
   189	    public function cards(): HasMany
   190	    {
   191	        return $this->hasMany(Card::class);
   192	    }
   193	
   194	    public function documents(): HasMany
   195	    {
   196	        return $this->hasMany(Document::class);
   197	    }
   198	
   199	    public function transfers(): HasMany
   200	    {
   201	        return $this->hasMany(Transfer::class);
   202	    }
   203	
   204	    public function boardOffers(): HasMany
   205	    {
   206	        return $this->hasMany(BoardOffer::class);
   207	    }
   208	
   209	    public function interviews(): HasMany
   210	    {
   211	        return $this->hasMany(Interview::class);
   212	    }
   213	
   214	    public function subscriptions(): HasMany
   215	    {
   216	        return $this->hasMany(Subscription::class);
   217	    }
   218	
   219	    public function installmentPlans(): HasMany
   220	    {
   221	        return $this->hasMany(InstallmentPlan::class);
   222	    }
   223	
   224	    public function bookings(): HasMany
   225	    {
   226	        return $this->hasMany(Booking::class);
   227	    }
   228	
   229	    public function workflowProgress(): HasMany
   230	    {
   231	        return $this->hasMany(WorkflowProgress::class);
   232	    }
   233	
   234	    public function activeCard(): HasOne
   235	    {
   236	        return $this->hasOne(Card::class)->where('status', 'active')->latestOfMany();
   237	    }
   238	
   239	    public function activeSuspension(): HasOne
   240	    {
   241	        return $this->hasOne(MemberSuspension::class)->where('status', 'active')->latestOfMany();
   242	    }
   243	
   244	    public function lastReceipt(): BelongsTo
   245	    {
   246	        return $this->belongsTo(Receipt::class, 'last_payment_receipt_id');
   247	    }
   248	
   249	    public function createdBy(): BelongsTo
   250	    {
   251	        return $this->belongsTo(User::class, 'created_by');
   252	    }
   253	
   254	    public function updatedBy(): BelongsTo
   255	    {
   256	        return $this->belongsTo(User::class, 'updated_by');
   257	    }
   258	
   259	    // ─── Scopes ──────────────────────────────────────────────────
   260	
   261	    public function scopeActive($query)
   262	    {
   263	        return $query->whereHas('status', fn ($q) => $q->where('is_active_status', true));
   264	    }
   265	
   266	    public function scopePending($query)
   267	    {
   268	        return $query->whereHas('status', fn ($q) => $q->where('is_pending_status', true));
   269	    }
   270	
   271	    public function scopeByType($query, int $typeId)
   272	    {
   273	        return $query->where('membership_type_id', $typeId);
   274	    }
   275	
   276	    public function scopeSearch($query, string $search)
   277	    {
   278	        return $query->where(function ($q) use ($search) {
   279	            $q->where('full_name_ar', 'like', "%{$search}%")
   280	              ->orWhere('full_name_en', 'like', "%{$search}%")
   281	              ->orWhere('membership_number', 'like', "%{$search}%")
   282	              ->orWhere('national_id', 'like', "%{$search}%")
   283	              ->orWhere('phone_primary', 'like', "%{$search}%");
   284	        });
   285	    }
   286	
   287	    public function scopeExpiredSubscription($query, int $year)
   288	    {
   289	        return $query->where(function ($q) use ($year) {
   290	            $q->whereNull('paid_through_year')
   291	              ->orWhere('paid_through_year', '<', $year);
   292	        });
   293	    }
   294	
   295	    // ─── Computed ────────────────────────────────────────────────
   296	
   297	    public function getAgeAttribute(): ?int
   298	    {
   299	        return $this->date_of_birth?->age;
   300	    }
   301	
   302	    public function getIsActiveAttribute(): bool
   303	    {
   304	        return $this->status?->is_active_status ?? false;
   305	    }
   306	
   307	    public function getIsSuspendedAttribute(): bool
   308	    {
   309	        return $this->activeSuspension()->exists();
   310	    }
   311	
   312	    public function getDisplayNameAttribute(): string
   313	    {
   314	        $name = $this->full_name_ar;
   315	        if ($this->membership_number) {
   316	            $name = "[{$this->membership_number}] {$name}";
   317	        }
   318	        return $name;
   319	    }
   320	
   321	    // ─── Media ───────────────────────────────────────────────────
   322	
   323	    public function registerMediaCollections(): void
   324	    {
   325	        $this->addMediaCollection('profile_photo')
   326	            ->singleFile()
   327	            ->acceptsMimeTypes(['image/jpeg', 'image/png', 'image/webp']);
   328	
   329	        $this->addMediaCollection('documents');
   330	    }
   331	
   332	    public static function getAuditLabel(): string
   333	    {
   334	        return 'عضو';
   335	    }
   336	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [264]: app/Models/Member.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [265/494]: app/Models/MemberSuspension.php
│ LANGUAGE: php | LINES: 94 | SIZE: 2672 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	declare(strict_types=1);
     4	
     5	namespace App\Models;
     6	
     7	use App\Enums\SuspensionStatus;
     8	use Illuminate\Database\Eloquent\Factories\HasFactory;
     9	use Illuminate\Database\Eloquent\Model;
    10	use Illuminate\Database\Eloquent\Relations\BelongsTo;
    11	use Illuminate\Database\Eloquent\SoftDeletes;
    12	use Spatie\Activitylog\LogOptions;
    13	use Spatie\Activitylog\Traits\LogsActivity;
    14	
    15	class MemberSuspension extends Model
    16	{
    17	    use HasFactory, SoftDeletes, LogsActivity;
    18	
    19	    protected $guarded = ['id'];
    20	
    21	    protected $casts = [
    22	        'status'          => SuspensionStatus::class,
    23	        'start_date'      => 'date',
    24	        'end_date'        => 'date',
    25	        'actual_end_date' => 'date',
    26	        'lifted_at'       => 'datetime',
    27	        'extended_at'     => 'datetime',
    28	    ];
    29	
    30	    public function getActivitylogOptions(): LogOptions
    31	    {
    32	        return LogOptions::defaults()
    33	            ->logAll()
    34	            ->logOnlyDirty()
    35	            ->dontSubmitEmptyLogs();
    36	    }
    37	
    38	    // ─── Relationships ──────────────────────────────────
    39	
    40	    public function member(): BelongsTo
    41	    {
    42	        return $this->belongsTo(Member::class);
    43	    }
    44	
    45	    public function violation(): BelongsTo
    46	    {
    47	        return $this->belongsTo(Violation::class);
    48	    }
    49	
    50	    public function penalty(): BelongsTo
    51	    {
    52	        return $this->belongsTo(Penalty::class);
    53	    }
    54	
    55	    public function suspendedBy(): BelongsTo
    56	    {
    57	        return $this->belongsTo(User::class, 'suspended_by');
    58	    }
    59	
    60	    public function liftedBy(): BelongsTo
    61	    {
    62	        return $this->belongsTo(User::class, 'lifted_by');
    63	    }
    64	
    65	    public function extendedByUser(): BelongsTo
    66	    {
    67	        return $this->belongsTo(User::class, 'extended_by');
    68	    }
    69	
    70	    // ─── Scopes ─────────────────────────────────────────
    71	
    72	    public function scopeActive($query)
    73	    {
    74	        return $query->where('status', SuspensionStatus::ACTIVE);
    75	    }
    76	
    77	    public function scopeCurrentlyActive($query)
    78	    {
    79	        return $query->where('status', SuspensionStatus::ACTIVE)
    80	            ->where('start_date', '<=', now())
    81	            ->where(fn ($q) => $q->whereNull('end_date')->orWhere('end_date', '>=', now()));
    82	    }
    83	
    84	    public function scopeExpiringSoon($query, int $days = 7)
    85	    {
    86	        return $query->where('status', SuspensionStatus::ACTIVE)
    87	            ->whereNotNull('end_date')
    88	            ->whereBetween('end_date', [now(), now()->addDays($days)]);
    89	    }
    90	
    91	    public function scopeForMember($query, int $memberId)
    92	    {
    93	        return $query->where('member_id', $memberId);
    94	    }
    95	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [265]: app/Models/MemberSuspension.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [266/494]: app/Models/MembershipStatus.php
│ LANGUAGE: php | LINES: 44 | SIZE: 1042 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Models;
     4	
     5	use App\Models\Traits\HasActiveScope;
     6	use App\Models\Traits\HasBilingualName;
     7	use App\Models\Traits\HasDisplayOrder;
     8	use Illuminate\Database\Eloquent\Model;
     9	use Illuminate\Database\Eloquent\Relations\HasMany;
    10	
    11	class MembershipStatus extends Model
    12	{
    13	    use HasActiveScope, HasBilingualName, HasDisplayOrder;
    14	
    15	    public $timestamps = false;
    16	
    17	    protected $fillable = [
    18	        'status_key',
    19	        'name_ar',
    20	        'name_en',
    21	        'color',
    22	        'icon',
    23	        'is_active_status',
    24	        'is_pending_status',
    25	        'is_terminal_status',
    26	        'is_active',
    27	        'display_order',
    28	    ];
    29	
    30	    protected function casts(): array
    31	    {
    32	        return [
    33	            'is_active_status' => 'boolean',
    34	            'is_pending_status' => 'boolean',
    35	            'is_terminal_status' => 'boolean',
    36	            'is_active' => 'boolean',
    37	            'display_order' => 'integer',
    38	        ];
    39	    }
    40	
    41	    public function members(): HasMany
    42	    {
    43	        return $this->hasMany(Member::class, 'status_id');
    44	    }
    45	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [266]: app/Models/MembershipStatus.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [267/494]: app/Models/MembershipType.php
│ LANGUAGE: php | LINES: 65 | SIZE: 1721 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Models;
     4	
     5	use App\Models\Traits\HasActiveScope;
     6	use App\Models\Traits\HasAuditTrail;
     7	use App\Models\Traits\HasBilingualName;
     8	use App\Models\Traits\HasDisplayOrder;
     9	use Illuminate\Database\Eloquent\Model;
    10	use Illuminate\Database\Eloquent\Relations\HasMany;
    11	
    12	class MembershipType extends Model
    13	{
    14	    use HasActiveScope, HasAuditTrail, HasBilingualName, HasDisplayOrder;
    15	
    16	    protected $fillable = [
    17	        'type_key',
    18	        'name_ar',
    19	        'name_en',
    20	        'description_ar',
    21	        'description_en',
    22	        'max_dependents',
    23	        'allows_voting',
    24	        'allows_board_membership',
    25	        'requires_sponsor',
    26	        'sponsor_count',
    27	        'requires_interview',
    28	        'requires_board_approval',
    29	        'has_expiry',
    30	        'default_duration_months',
    31	        'is_active',
    32	        'display_order',
    33	    ];
    34	
    35	    protected function casts(): array
    36	    {
    37	        return [
    38	            'max_dependents' => 'integer',
    39	            'allows_voting' => 'boolean',
    40	            'allows_board_membership' => 'boolean',
    41	            'requires_sponsor' => 'boolean',
    42	            'sponsor_count' => 'integer',
    43	            'requires_interview' => 'boolean',
    44	            'requires_board_approval' => 'boolean',
    45	            'has_expiry' => 'boolean',
    46	            'default_duration_months' => 'integer',
    47	            'is_active' => 'boolean',
    48	            'display_order' => 'integer',
    49	        ];
    50	    }
    51	
    52	    public function members(): HasMany
    53	    {
    54	        return $this->hasMany(Member::class);
    55	    }
    56	
    57	    public function feeSchedules(): HasMany
    58	    {
    59	        return $this->hasMany(FeeSchedule::class);
    60	    }
    61	
    62	    public static function getAuditLabel(): string
    63	    {
    64	        return 'نوع عضوية';
    65	    }
    66	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [267]: app/Models/MembershipType.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [268/494]: app/Models/Nationality.php
│ LANGUAGE: php | LINES: 30 | SIZE: 614 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Models;
     4	
     5	use App\Models\Traits\HasActiveScope;
     6	use App\Models\Traits\HasBilingualName;
     7	use App\Models\Traits\HasDisplayOrder;
     8	use Illuminate\Database\Eloquent\Model;
     9	
    10	class Nationality extends Model
    11	{
    12	    use HasActiveScope, HasBilingualName, HasDisplayOrder;
    13	
    14	    public $timestamps = false;
    15	
    16	    protected $fillable = [
    17	        'name_ar',
    18	        'name_en',
    19	        'iso_code',
    20	        'is_active',
    21	        'display_order',
    22	    ];
    23	
    24	    protected function casts(): array
    25	    {
    26	        return [
    27	            'is_active' => 'boolean',
    28	            'display_order' => 'integer',
    29	        ];
    30	    }
    31	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [268]: app/Models/Nationality.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [269/494]: app/Models/Notification.php
│ LANGUAGE: php | LINES: 75 | SIZE: 1637 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Models;
     4	
     5	use App\Enums\NotificationChannel;
     6	use App\Enums\NotificationPriority;
     7	use Illuminate\Database\Eloquent\Factories\HasFactory;
     8	use Illuminate\Database\Eloquent\Model;
     9	use Illuminate\Database\Eloquent\Relations\MorphTo;
    10	
    11	class Notification extends Model
    12	{
    13	    use HasFactory;
    14	
    15	    protected $table = 'club_notifications';
    16	
    17	    protected $fillable = [
    18	        'notifiable_type',
    19	        'notifiable_id',
    20	        'channel',
    21	        'priority',
    22	        'title_ar',
    23	        'title_en',
    24	        'body_ar',
    25	        'body_en',
    26	        'action_url',
    27	        'category',
    28	        'data',
    29	        'sent_at',
    30	        'read_at',
    31	    ];
    32	
    33	    protected $casts = [
    34	        'channel' => NotificationChannel::class,
    35	        'priority' => NotificationPriority::class,
    36	        'data' => 'array',
    37	        'sent_at' => 'datetime',
    38	        'read_at' => 'datetime',
    39	    ];
    40	
    41	    public function notifiable(): MorphTo
    42	    {
    43	        return $this->morphTo();
    44	    }
    45	
    46	    public function scopeUnread($query)
    47	    {
    48	        return $query->whereNull('read_at');
    49	    }
    50	
    51	    public function scopeRead($query)
    52	    {
    53	        return $query->whereNotNull('read_at');
    54	    }
    55	
    56	    public function scopeForChannel($query, NotificationChannel $channel)
    57	    {
    58	        return $query->where('channel', $channel);
    59	    }
    60	
    61	    public function scopeForCategory($query, string $category)
    62	    {
    63	        return $query->where('category', $category);
    64	    }
    65	
    66	    public function isRead(): bool
    67	    {
    68	        return $this->read_at !== null;
    69	    }
    70	
    71	    public function markAsRead(): self
    72	    {
    73	        $this->update(['read_at' => now()]);
    74	        return $this;
    75	    }
    76	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [269]: app/Models/Notification.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [270/494]: app/Models/NotificationTemplate.php
│ LANGUAGE: php | LINES: 33 | SIZE: 659 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Models;
     4	
     5	use App\Enums\NotificationChannel;
     6	use App\Models\Traits\HasActiveScope;
     7	use Illuminate\Database\Eloquent\Model;
     8	
     9	class NotificationTemplate extends Model
    10	{
    11	    use HasActiveScope;
    12	
    13	    protected $fillable = [
    14	        'template_key',
    15	        'name_ar',
    16	        'name_en',
    17	        'channel',
    18	        'subject_ar',
    19	        'subject_en',
    20	        'body_ar',
    21	        'body_en',
    22	        'variables',
    23	        'is_active',
    24	    ];
    25	
    26	    protected function casts(): array
    27	    {
    28	        return [
    29	            'channel' => NotificationChannel::class,
    30	            'variables' => 'array',
    31	            'is_active' => 'boolean',
    32	        ];
    33	    }
    34	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [270]: app/Models/NotificationTemplate.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [271/494]: app/Models/NumberingSequence.php
│ LANGUAGE: php | LINES: 42 | SIZE: 956 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Models;
     4	
     5	use Illuminate\Database\Eloquent\Model;
     6	use Illuminate\Database\Eloquent\Relations\BelongsTo;
     7	
     8	class NumberingSequence extends Model
     9	{
    10	    public $timestamps = false;
    11	
    12	    protected $fillable = [
    13	        'sequence_key',
    14	        'prefix',
    15	        'suffix',
    16	        'current_value',
    17	        'start_value',
    18	        'increment_by',
    19	        'pad_length',
    20	        'fiscal_year_id',
    21	        'reset_on_fiscal_year',
    22	        'description_ar',
    23	        'description_en',
    24	        'is_active',
    25	    ];
    26	
    27	    protected function casts(): array
    28	    {
    29	        return [
    30	            'current_value' => 'integer',
    31	            'start_value' => 'integer',
    32	            'increment_by' => 'integer',
    33	            'pad_length' => 'integer',
    34	            'reset_on_fiscal_year' => 'boolean',
    35	            'is_active' => 'boolean',
    36	        ];
    37	    }
    38	
    39	    public function fiscalYear(): BelongsTo
    40	    {
    41	        return $this->belongsTo(FiscalYear::class);
    42	    }
    43	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [271]: app/Models/NumberingSequence.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [272/494]: app/Models/Penalty.php
│ LANGUAGE: php | LINES: 96 | SIZE: 2732 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	declare(strict_types=1);
     4	
     5	namespace App\Models;
     6	
     7	use App\Enums\PenaltyStatus;
     8	use Illuminate\Database\Eloquent\Factories\HasFactory;
     9	use Illuminate\Database\Eloquent\Model;
    10	use Illuminate\Database\Eloquent\Relations\BelongsTo;
    11	use Illuminate\Database\Eloquent\Relations\HasMany;
    12	use Illuminate\Database\Eloquent\SoftDeletes;
    13	use Spatie\Activitylog\LogOptions;
    14	use Spatie\Activitylog\Traits\LogsActivity;
    15	
    16	class Penalty extends Model
    17	{
    18	    use HasFactory, SoftDeletes, LogsActivity;
    19	
    20	    protected $guarded = ['id'];
    21	
    22	    protected $casts = [
    23	        'status'              => PenaltyStatus::class,
    24	        'imposed_date'        => 'date',
    25	        'effective_from'      => 'date',
    26	        'effective_until'     => 'date',
    27	        'fine_amount'         => 'decimal:2',
    28	        'fine_paid'           => 'boolean',
    29	        'fine_paid_at'        => 'datetime',
    30	        'requires_suspension' => 'boolean',
    31	        'completed_at'        => 'datetime',
    32	        'waived_at'           => 'datetime',
    33	        'overturned_at'       => 'datetime',
    34	    ];
    35	
    36	    public function getActivitylogOptions(): LogOptions
    37	    {
    38	        return LogOptions::defaults()
    39	            ->logAll()
    40	            ->logOnlyDirty()
    41	            ->dontSubmitEmptyLogs();
    42	    }
    43	
    44	    // ─── Relationships ──────────────────────────────────
    45	
    46	    public function violation(): BelongsTo
    47	    {
    48	        return $this->belongsTo(Violation::class);
    49	    }
    50	
    51	    public function member(): BelongsTo
    52	    {
    53	        return $this->belongsTo(Member::class);
    54	    }
    55	
    56	    public function penaltyType(): BelongsTo
    57	    {
    58	        return $this->belongsTo(PenaltyType::class);
    59	    }
    60	
    61	    public function imposedBy(): BelongsTo
    62	    {
    63	        return $this->belongsTo(User::class, 'imposed_by');
    64	    }
    65	
    66	    public function waivedByUser(): BelongsTo
    67	    {
    68	        return $this->belongsTo(User::class, 'waived_by');
    69	    }
    70	
    71	    public function overturnedByUser(): BelongsTo
    72	    {
    73	        return $this->belongsTo(User::class, 'overturned_by');
    74	    }
    75	
    76	    public function suspensions(): HasMany
    77	    {
    78	        return $this->hasMany(MemberSuspension::class);
    79	    }
    80	
    81	    // ─── Scopes ─────────────────────────────────────────
    82	
    83	    public function scopeActive($query)
    84	    {
    85	        return $query->whereIn('status', [PenaltyStatus::ACTIVE, PenaltyStatus::ENFORCED]);
    86	    }
    87	
    88	    public function scopeWithUnpaidFines($query)
    89	    {
    90	        return $query->where('fine_amount', '>', 0)->where('fine_paid', false);
    91	    }
    92	
    93	    public function scopeForMember($query, int $memberId)
    94	    {
    95	        return $query->where('member_id', $memberId);
    96	    }
    97	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [272]: app/Models/Penalty.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [273/494]: app/Models/PenaltyType.php
│ LANGUAGE: php | LINES: 28 | SIZE: 669 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	declare(strict_types=1);
     4	
     5	namespace App\Models;
     6	
     7	use Illuminate\Database\Eloquent\Factories\HasFactory;
     8	use Illuminate\Database\Eloquent\Model;
     9	use Illuminate\Database\Eloquent\Relations\HasMany;
    10	
    11	class PenaltyType extends Model
    12	{
    13	    use HasFactory;
    14	
    15	    protected $guarded = ['id'];
    16	
    17	    protected $casts = [
    18	        'default_fine_amount'     => 'decimal:2',
    19	        'requires_suspension'     => 'boolean',
    20	        'default_suspension_days' => 'integer',
    21	        'requires_board_approval' => 'boolean',
    22	        'is_active'               => 'boolean',
    23	    ];
    24	
    25	    public function penalties(): HasMany
    26	    {
    27	        return $this->hasMany(Penalty::class);
    28	    }
    29	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [273]: app/Models/PenaltyType.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [274/494]: app/Models/Receipt.php
│ LANGUAGE: php | LINES: 159 | SIZE: 3792 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Models;
     4	
     5	use App\Models\Traits\Archivable;
     6	use Illuminate\Database\Eloquent\Model;
     7	use Illuminate\Database\Eloquent\Relations\BelongsTo;
     8	use Illuminate\Database\Eloquent\Relations\HasMany;
     9	use Spatie\Activitylog\Traits\LogsActivity;
    10	use Spatie\Activitylog\LogOptions;
    11	
    12	class Receipt extends Model
    13	{
    14	    use Archivable, LogsActivity;
    15	
    16	    protected $fillable = [
    17	        'receipt_number',
    18	        'member_id',
    19	        'subscription_id',
    20	        'installment_plan_id',
    21	        'installment_id',
    22	        'cash_register_id',
    23	        'fee_category',
    24	        'receipt_date',
    25	        'subtotal',
    26	        'discount_amount',
    27	        'discount_reason',
    28	        'board_offer_id',
    29	        'vat_rate',
    30	        'vat_amount',
    31	        'total_amount',
    32	        'amount_paid',
    33	        'amount_remaining',
    34	        'payment_method',
    35	        'payment_reference',
    36	        'status',
    37	        'notes_ar',
    38	        'notes_en',
    39	        'void_reason',
    40	        'voided_by',
    41	        'voided_at',
    42	        'created_by',
    43	        'fiscal_year',
    44	        'is_archived',
    45	    ];
    46	
    47	    protected $casts = [
    48	        'receipt_date' => 'date',
    49	        'subtotal' => 'decimal:2',
    50	        'discount_amount' => 'decimal:2',
    51	        'vat_rate' => 'decimal:2',
    52	        'vat_amount' => 'decimal:2',
    53	        'total_amount' => 'decimal:2',
    54	        'amount_paid' => 'decimal:2',
    55	        'amount_remaining' => 'decimal:2',
    56	        'voided_at' => 'datetime',
    57	        'is_archived' => 'boolean',
    58	    ];
    59	
    60	    public function getActivitylogOptions(): LogOptions
    61	    {
    62	        return LogOptions::defaults()
    63	            ->logAll()
    64	            ->logOnlyDirty()
    65	            ->useLogName('receipts');
    66	    }
    67	
    68	    // Relationships
    69	    public function member(): BelongsTo
    70	    {
    71	        return $this->belongsTo(Member::class);
    72	    }
    73	
    74	    public function subscription(): BelongsTo
    75	    {
    76	        return $this->belongsTo(Subscription::class);
    77	    }
    78	
    79	    public function installmentPlan(): BelongsTo
    80	    {
    81	        return $this->belongsTo(InstallmentPlan::class);
    82	    }
    83	
    84	    public function installment(): BelongsTo
    85	    {
    86	        return $this->belongsTo(Installment::class);
    87	    }
    88	
    89	    public function cashRegister(): BelongsTo
    90	    {
    91	        return $this->belongsTo(CashRegister::class);
    92	    }
    93	
    94	    public function items(): HasMany
    95	    {
    96	        return $this->hasMany(ReceiptItem::class)->orderBy('sort_order');
    97	    }
    98	
    99	    public function createdBy(): BelongsTo
   100	    {
   101	        return $this->belongsTo(User::class, 'created_by');
   102	    }
   103	
   104	    public function voidedBy(): BelongsTo
   105	    {
   106	        return $this->belongsTo(User::class, 'voided_by');
   107	    }
   108	
   109	    // Scopes
   110	    public function scopePaid($query)
   111	    {
   112	        return $query->where('status', 'paid');
   113	    }
   114	
   115	    public function scopeNotVoided($query)
   116	    {
   117	        return $query->whereNotIn('status', ['voided', 'cancelled']);
   118	    }
   119	
   120	    public function scopeForYear($query, int $year)
   121	    {
   122	        return $query->where('fiscal_year', $year);
   123	    }
   124	
   125	    public function scopeForMember($query, int $memberId)
   126	    {
   127	        return $query->where('member_id', $memberId);
   128	    }
   129	
   130	    public function scopeForCategory($query, string $category)
   131	    {
   132	        return $query->where('fee_category', $category);
   133	    }
   134	
   135	    public function scopeToday($query)
   136	    {
   137	        return $query->whereDate('receipt_date', now()->toDateString());
   138	    }
   139	
   140	    // Helpers
   141	    public function isPaid(): bool
   142	    {
   143	        return $this->status === 'paid';
   144	    }
   145	
   146	    public function isVoided(): bool
   147	    {
   148	        return $this->status === 'voided';
   149	    }
   150	
   151	    public function canBeVoided(): bool
   152	    {
   153	        return !$this->isVoided() && $this->status !== 'cancelled';
   154	    }
   155	
   156	    public function getFormattedTotalAttribute(): string
   157	    {
   158	        return number_format($this->total_amount, 2) . ' جنيه';
   159	    }
   160	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [274]: app/Models/Receipt.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [275/494]: app/Models/ReceiptItem.php
│ LANGUAGE: php | LINES: 31 | SIZE: 645 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Models;
     4	
     5	use Illuminate\Database\Eloquent\Model;
     6	use Illuminate\Database\Eloquent\Relations\BelongsTo;
     7	
     8	class ReceiptItem extends Model
     9	{
    10	    protected $fillable = [
    11	        'receipt_id',
    12	        'fee_category',
    13	        'description_ar',
    14	        'description_en',
    15	        'amount',
    16	        'quantity',
    17	        'subtotal',
    18	        'sort_order',
    19	    ];
    20	
    21	    protected $casts = [
    22	        'amount' => 'decimal:2',
    23	        'subtotal' => 'decimal:2',
    24	        'quantity' => 'integer',
    25	        'sort_order' => 'integer',
    26	    ];
    27	
    28	    public function receipt(): BelongsTo
    29	    {
    30	        return $this->belongsTo(Receipt::class);
    31	    }
    32	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [275]: app/Models/ReceiptItem.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [276/494]: app/Models/RelationshipType.php
│ LANGUAGE: php | LINES: 32 | SIZE: 681 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Models;
     4	
     5	use App\Models\Traits\HasActiveScope;
     6	use App\Models\Traits\HasBilingualName;
     7	use App\Models\Traits\HasDisplayOrder;
     8	use Illuminate\Database\Eloquent\Model;
     9	
    10	class RelationshipType extends Model
    11	{
    12	    use HasActiveScope, HasBilingualName, HasDisplayOrder;
    13	
    14	    public $timestamps = false;
    15	
    16	    protected $fillable = [
    17	        'name_ar',
    18	        'name_en',
    19	        'gender_specific',
    20	        'max_age',
    21	        'is_active',
    22	        'display_order',
    23	    ];
    24	
    25	    protected function casts(): array
    26	    {
    27	        return [
    28	            'max_age' => 'integer',
    29	            'is_active' => 'boolean',
    30	            'display_order' => 'integer',
    31	        ];
    32	    }
    33	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [276]: app/Models/RelationshipType.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [277/494]: app/Models/Religion.php
│ LANGUAGE: php | LINES: 29 | SIZE: 591 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Models;
     4	
     5	use App\Models\Traits\HasActiveScope;
     6	use App\Models\Traits\HasBilingualName;
     7	use App\Models\Traits\HasDisplayOrder;
     8	use Illuminate\Database\Eloquent\Model;
     9	
    10	class Religion extends Model
    11	{
    12	    use HasActiveScope, HasBilingualName, HasDisplayOrder;
    13	
    14	    public $timestamps = false;
    15	
    16	    protected $fillable = [
    17	        'name_ar',
    18	        'name_en',
    19	        'is_active',
    20	        'display_order',
    21	    ];
    22	
    23	    protected function casts(): array
    24	    {
    25	        return [
    26	            'is_active' => 'boolean',
    27	            'display_order' => 'integer',
    28	        ];
    29	    }
    30	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [277]: app/Models/Religion.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [278/494]: app/Models/Sequence.php
│ LANGUAGE: php | LINES: 45 | SIZE: 1035 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Models;
     4	
     5	use Illuminate\Database\Eloquent\Factories\HasFactory;
     6	use Illuminate\Database\Eloquent\Model;
     7	
     8	class Sequence extends Model
     9	{
    10	    use HasFactory;
    11	
    12	    protected $fillable = [
    13	        'sequence_key',
    14	        'prefix',
    15	        'suffix',
    16	        'current_value',
    17	        'start_value',
    18	        'increment_by',
    19	        'pad_length',
    20	        'reset_on_fiscal_year',
    21	        'last_reset_at',
    22	        'description_ar',
    23	        'description_en',
    24	    ];
    25	
    26	    protected $casts = [
    27	        'current_value' => 'integer',
    28	        'start_value' => 'integer',
    29	        'increment_by' => 'integer',
    30	        'pad_length' => 'integer',
    31	        'reset_on_fiscal_year' => 'boolean',
    32	        'last_reset_at' => 'datetime',
    33	    ];
    34	
    35	    public function getFormattedCurrentAttribute(): string
    36	    {
    37	        $paddedNumber = str_pad(
    38	            (string) $this->current_value,
    39	            $this->pad_length,
    40	            '0',
    41	            STR_PAD_LEFT
    42	        );
    43	
    44	        return $this->prefix . $paddedNumber . $this->suffix;
    45	    }
    46	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [278]: app/Models/Sequence.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [279/494]: app/Models/Subscription.php
│ LANGUAGE: php | LINES: 271 | SIZE: 8286 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Models;
     4	
     5	use App\Enums\SubscriptionStatus;
     6	use Illuminate\Database\Eloquent\Factories\HasFactory;
     7	use Illuminate\Database\Eloquent\Model;
     8	use Illuminate\Database\Eloquent\Relations\BelongsTo;
     9	use Illuminate\Database\Eloquent\Relations\HasMany;
    10	use Illuminate\Database\Eloquent\SoftDeletes;
    11	use Spatie\Activitylog\LogOptions;
    12	use Spatie\Activitylog\Traits\LogsActivity;
    13	
    14	class Subscription extends Model
    15	{
    16	    use HasFactory, SoftDeletes, LogsActivity;
    17	
    18	    protected $fillable = [
    19	        'member_id',
    20	        'subscription_period_id',
    21	        'status',
    22	        'base_amount',
    23	        'dependents_amount',
    24	        'total_amount',
    25	        'discount_amount',
    26	        'late_fee_amount',
    27	        'net_amount',
    28	        'paid_amount',
    29	        'balance',
    30	        'discount_reason',
    31	        'discount_approved_by',
    32	        'due_date',
    33	        'paid_date',
    34	        'late_applied_date',
    35	        'is_late',
    36	        'is_waived',
    37	        'waiver_reason',
    38	        'waived_by',
    39	        'waived_date',
    40	        'dependents_breakdown',
    41	        'notes',
    42	        'created_by',
    43	        'updated_by',
    44	    ];
    45	
    46	    protected $casts = [
    47	        'status'                => SubscriptionStatus::class,
    48	        'base_amount'           => 'decimal:2',
    49	        'dependents_amount'     => 'decimal:2',
    50	        'total_amount'          => 'decimal:2',
    51	        'discount_amount'       => 'decimal:2',
    52	        'late_fee_amount'       => 'decimal:2',
    53	        'net_amount'            => 'decimal:2',
    54	        'paid_amount'           => 'decimal:2',
    55	        'balance'               => 'decimal:2',
    56	        'due_date'              => 'date',
    57	        'paid_date'             => 'date',
    58	        'late_applied_date'     => 'date',
    59	        'waived_date'           => 'date',
    60	        'is_late'               => 'boolean',
    61	        'is_waived'             => 'boolean',
    62	        'dependents_breakdown'  => 'array',
    63	    ];
    64	
    65	    // ─── RELATIONSHIPS ────────────────────────────────────────────
    66	
    67	    public function member(): BelongsTo
    68	    {
    69	        return $this->belongsTo(Member::class);
    70	    }
    71	
    72	    public function subscriptionPeriod(): BelongsTo
    73	    {
    74	        return $this->belongsTo(SubscriptionPeriod::class);
    75	    }
    76	
    77	    public function payments(): HasMany
    78	    {
    79	        return $this->hasMany(SubscriptionPayment::class);
    80	    }
    81	
    82	    public function activePayments(): HasMany
    83	    {
    84	        return $this->hasMany(SubscriptionPayment::class)->where('is_reversed', false);
    85	    }
    86	
    87	    public function discountApprover(): BelongsTo
    88	    {
    89	        return $this->belongsTo(User::class, 'discount_approved_by');
    90	    }
    91	
    92	    public function waiver(): BelongsTo
    93	    {
    94	        return $this->belongsTo(User::class, 'waived_by');
    95	    }
    96	
    97	    public function creator(): BelongsTo
    98	    {
    99	        return $this->belongsTo(User::class, 'created_by');
   100	    }
   101	
   102	    public function updater(): BelongsTo
   103	    {
   104	        return $this->belongsTo(User::class, 'updated_by');
   105	    }
   106	
   107	    // ─── SCOPES ───────────────────────────────────────────────────
   108	
   109	    public function scopeForMember($query, int $memberId)
   110	    {
   111	        return $query->where('member_id', $memberId);
   112	    }
   113	
   114	    public function scopeForPeriod($query, int $periodId)
   115	    {
   116	        return $query->where('subscription_period_id', $periodId);
   117	    }
   118	
   119	    public function scopePending($query)
   120	    {
   121	        return $query->where('status', SubscriptionStatus::PENDING);
   122	    }
   123	
   124	    public function scopeOverdue($query)
   125	    {
   126	        return $query->where('status', SubscriptionStatus::OVERDUE);
   127	    }
   128	
   129	    public function scopePaid($query)
   130	    {
   131	        return $query->where('status', SubscriptionStatus::PAID);
   132	    }
   133	
   134	    public function scopeUnpaid($query)
   135	    {
   136	        return $query->whereIn('status', [
   137	            SubscriptionStatus::PENDING,
   138	            SubscriptionStatus::PARTIAL,
   139	            SubscriptionStatus::OVERDUE,
   140	        ]);
   141	    }
   142	
   143	    public function scopePayable($query)
   144	    {
   145	        return $query->whereIn('status', SubscriptionStatus::payableStatuses());
   146	    }
   147	
   148	    public function scopeLate($query)
   149	    {
   150	        return $query->where('is_late', true);
   151	    }
   152	
   153	    public function scopeForYear($query, int $year)
   154	    {
   155	        return $query->whereHas('subscriptionPeriod', fn ($q) => $q->where('year', $year));
   156	    }
   157	
   158	    // ─── ACCESSORS ────────────────────────────────────────────────
   159	
   160	    public function getIsPayableAttribute(): bool
   161	    {
   162	        return $this->status->isPayable();
   163	    }
   164	
   165	    public function getIsFullyPaidAttribute(): bool
   166	    {
   167	        return $this->status === SubscriptionStatus::PAID;
   168	    }
   169	
   170	    public function getRemainingBalanceAttribute(): float
   171	    {
   172	        return max(0, $this->net_amount - $this->paid_amount);
   173	    }
   174	
   175	    public function getPaymentProgressAttribute(): float
   176	    {
   177	        if ($this->net_amount <= 0) return 100;
   178	        return min(100, round(($this->paid_amount / $this->net_amount) * 100, 2));
   179	    }
   180	
   181	    public function getPeriodYearAttribute(): ?int
   182	    {
   183	        return $this->subscriptionPeriod?->year;
   184	    }
   185	
   186	    public function getStatusBadgeAttribute(): string
   187	    {
   188	        return $this->status->label();
   189	    }
   190	
   191	    public function getStatusColorAttribute(): string
   192	    {
   193	        return $this->status->color();
   194	    }
   195	
   196	    // ─── METHODS ──────────────────────────────────────────────────
   197	
   198	    public function recalculateBalance(): void
   199	    {
   200	        $paidAmount = $this->activePayments()->sum('amount');
   201	        $this->paid_amount = $paidAmount;
   202	        $this->balance = max(0, $this->net_amount - $paidAmount);
   203	
   204	        if ($paidAmount >= $this->net_amount && $this->net_amount > 0) {
   205	            $this->status = SubscriptionStatus::PAID;
   206	            $this->paid_date = $this->paid_date ?? now();
   207	        } elseif ($paidAmount > 0) {
   208	            $this->status = SubscriptionStatus::PARTIAL;
   209	        }
   210	
   211	        $this->save();
   212	    }
   213	
   214	    public function recalculateAmounts(): void
   215	    {
   216	        $this->total_amount = $this->base_amount + $this->dependents_amount;
   217	        $this->net_amount = $this->total_amount - $this->discount_amount + $this->late_fee_amount;
   218	        $this->balance = max(0, $this->net_amount - $this->paid_amount);
   219	        $this->save();
   220	    }
   221	
   222	    public function applyLateFee(): void
   223	    {
   224	        if ($this->is_late || $this->is_waived || $this->status->isTerminal()) {
   225	            return;
   226	        }
   227	
   228	        $period = $this->subscriptionPeriod;
   229	        $lateFee = $period->calculateLateFee($this->total_amount);
   230	
   231	        if ($lateFee > 0) {
   232	            $this->late_fee_amount = $lateFee;
   233	            $this->is_late = true;
   234	            $this->late_applied_date = now();
   235	            $this->status = SubscriptionStatus::OVERDUE;
   236	            $this->recalculateAmounts();
   237	        }
   238	    }
   239	
   240	    public function waive(int $userId, string $reason): void
   241	    {
   242	        $this->is_waived = true;
   243	        $this->waived_by = $userId;
   244	        $this->waived_date = now();
   245	        $this->waiver_reason = $reason;
   246	        $this->status = SubscriptionStatus::WAIVED;
   247	        $this->balance = 0;
   248	        $this->save();
   249	    }
   250	
   251	    public function cancel(): void
   252	    {
   253	        if ($this->paid_amount > 0) {
   254	            throw new \DomainException('لا يمكن إلغاء اشتراك تم دفع جزء منه. قم بعمل استرداد أولاً.');
   255	        }
   256	
   257	        $this->status = SubscriptionStatus::CANCELLED;
   258	        $this->balance = 0;
   259	        $this->save();
   260	    }
   261	
   262	    // ─── ACTIVITY LOG ─────────────────────────────────────────────
   263	
   264	    public function getActivitylogOptions(): LogOptions
   265	    {
   266	        return LogOptions::defaults()
   267	            ->logAll()
   268	            ->logOnlyDirty()
   269	            ->useLogName('subscriptions')
   270	            ->setDescriptionForEvent(fn (string $eventName) => "Subscription #{$this->id} for Member #{$this->member_id} was {$eventName}");
   271	    }
   272	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [279]: app/Models/Subscription.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [280/494]: app/Models/SubscriptionPayment.php
│ LANGUAGE: php | LINES: 101 | SIZE: 3130 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Models;
     4	
     5	use App\Enums\PaymentMethod;
     6	use Illuminate\Database\Eloquent\Factories\HasFactory;
     7	use Illuminate\Database\Eloquent\Model;
     8	use Illuminate\Database\Eloquent\Relations\BelongsTo;
     9	use Illuminate\Database\Eloquent\SoftDeletes;
    10	use Spatie\Activitylog\LogOptions;
    11	use Spatie\Activitylog\Traits\LogsActivity;
    12	
    13	class SubscriptionPayment extends Model
    14	{
    15	    use HasFactory, SoftDeletes, LogsActivity;
    16	
    17	    protected $fillable = [
    18	        'subscription_id',
    19	        'receipt_id',
    20	        'amount',
    21	        'payment_date',
    22	        'payment_method',
    23	        'reference_number',
    24	        'notes',
    25	        'is_reversed',
    26	        'reversed_by',
    27	        'reversed_at',
    28	        'reversal_reason',
    29	        'created_by',
    30	    ];
    31	
    32	    protected $casts = [
    33	        'amount'         => 'decimal:2',
    34	        'payment_date'   => 'date',
    35	        'payment_method' => PaymentMethod::class,
    36	        'is_reversed'    => 'boolean',
    37	        'reversed_at'    => 'datetime',
    38	    ];
    39	
    40	    // ─── RELATIONSHIPS ────────────────────────────────────────────
    41	
    42	    public function subscription(): BelongsTo
    43	    {
    44	        return $this->belongsTo(Subscription::class);
    45	    }
    46	
    47	    public function receipt(): BelongsTo
    48	    {
    49	        return $this->belongsTo(Receipt::class);
    50	    }
    51	
    52	    public function creator(): BelongsTo
    53	    {
    54	        return $this->belongsTo(User::class, 'created_by');
    55	    }
    56	
    57	    public function reverser(): BelongsTo
    58	    {
    59	        return $this->belongsTo(User::class, 'reversed_by');
    60	    }
    61	
    62	    // ─── SCOPES ───────────────────────────────────────────────────
    63	
    64	    public function scopeActive($query)
    65	    {
    66	        return $query->where('is_reversed', false);
    67	    }
    68	
    69	    public function scopeReversed($query)
    70	    {
    71	        return $query->where('is_reversed', true);
    72	    }
    73	
    74	    // ─── METHODS ──────────────────────────────────────────────────
    75	
    76	    public function reverse(int $userId, string $reason): void
    77	    {
    78	        if ($this->is_reversed) {
    79	            throw new \DomainException('هذا الدفع تم عكسه بالفعل.');
    80	        }
    81	
    82	        $this->is_reversed = true;
    83	        $this->reversed_by = $userId;
    84	        $this->reversed_at = now();
    85	        $this->reversal_reason = $reason;
    86	        $this->save();
    87	
    88	        // Recalculate parent subscription
    89	        $this->subscription->recalculateBalance();
    90	    }
    91	
    92	    // ─── ACTIVITY LOG ─────────────────────────────────────────────
    93	
    94	    public function getActivitylogOptions(): LogOptions
    95	    {
    96	        return LogOptions::defaults()
    97	            ->logAll()
    98	            ->logOnlyDirty()
    99	            ->useLogName('subscription_payments')
   100	            ->setDescriptionForEvent(fn (string $eventName) => "Subscription Payment #{$this->id} was {$eventName}");
   101	    }
   102	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [280]: app/Models/SubscriptionPayment.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [281/494]: app/Models/SubscriptionPeriod.php
│ LANGUAGE: php | LINES: 157 | SIZE: 4810 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Models;
     4	
     5	use Illuminate\Database\Eloquent\Factories\HasFactory;
     6	use Illuminate\Database\Eloquent\Model;
     7	use Illuminate\Database\Eloquent\Relations\BelongsTo;
     8	use Illuminate\Database\Eloquent\Relations\HasMany;
     9	use Illuminate\Database\Eloquent\SoftDeletes;
    10	use Spatie\Activitylog\LogOptions;
    11	use Spatie\Activitylog\Traits\LogsActivity;
    12	
    13	class SubscriptionPeriod extends Model
    14	{
    15	    use HasFactory, SoftDeletes, LogsActivity;
    16	
    17	    protected $fillable = [
    18	        'name_ar',
    19	        'name_en',
    20	        'year',
    21	        'start_date',
    22	        'end_date',
    23	        'grace_deadline',
    24	        'member_fee',
    25	        'spouse_fee',
    26	        'child_fee',
    27	        'late_fee',
    28	        'late_fee_percentage',
    29	        'grace_period_days',
    30	        'is_active',
    31	        'is_locked',
    32	        'auto_generate',
    33	        'notes',
    34	        'created_by',
    35	    ];
    36	
    37	    protected $casts = [
    38	        'year'                => 'integer',
    39	        'start_date'          => 'date',
    40	        'end_date'            => 'date',
    41	        'grace_deadline'      => 'date',
    42	        'member_fee'          => 'decimal:2',
    43	        'spouse_fee'          => 'decimal:2',
    44	        'child_fee'           => 'decimal:2',
    45	        'late_fee'            => 'decimal:2',
    46	        'late_fee_percentage' => 'decimal:2',
    47	        'grace_period_days'   => 'integer',
    48	        'is_active'           => 'boolean',
    49	        'is_locked'           => 'boolean',
    50	        'auto_generate'       => 'boolean',
    51	    ];
    52	
    53	    // ─── RELATIONSHIPS ────────────────────────────────────────────
    54	
    55	    public function subscriptions(): HasMany
    56	    {
    57	        return $this->hasMany(Subscription::class);
    58	    }
    59	
    60	    public function creator(): BelongsTo
    61	    {
    62	        return $this->belongsTo(User::class, 'created_by');
    63	    }
    64	
    65	    // ─── SCOPES ───────────────────────────────────────────────────
    66	
    67	    public function scopeActive($query)
    68	    {
    69	        return $query->where('is_active', true);
    70	    }
    71	
    72	    public function scopeCurrent($query)
    73	    {
    74	        return $query->where('start_date', '<=', now())
    75	                     ->where('end_date', '>=', now());
    76	    }
    77	
    78	    public function scopeForYear($query, int $year)
    79	    {
    80	        return $query->where('year', $year);
    81	    }
    82	
    83	    // ─── ACCESSORS ────────────────────────────────────────────────
    84	
    85	    public function getDisplayNameAttribute(): string
    86	    {
    87	        return app()->getLocale() === 'ar'
    88	            ? ($this->name_ar ?: "اشتراك {$this->year}")
    89	            : ($this->name_en ?: "Subscription {$this->year}");
    90	    }
    91	
    92	    public function getIsCurrentAttribute(): bool
    93	    {
    94	        return now()->between($this->start_date, $this->end_date);
    95	    }
    96	
    97	    public function getIsGracePeriodActiveAttribute(): bool
    98	    {
    99	        if (!$this->grace_deadline) {
   100	            return false;
   101	        }
   102	
   103	        return now()->lte($this->grace_deadline);
   104	    }
   105	
   106	    public function getIsOverdueAttribute(): bool
   107	    {
   108	        if ($this->grace_deadline) {
   109	            return now()->gt($this->grace_deadline);
   110	        }
   111	
   112	        return now()->gt($this->end_date);
   113	    }
   114	
   115	    public function getTotalSubscriptionsCountAttribute(): int
   116	    {
   117	        return $this->subscriptions()->count();
   118	    }
   119	
   120	    public function getPaidCountAttribute(): int
   121	    {
   122	        return $this->subscriptions()->where('status', 'paid')->count();
   123	    }
   124	
   125	    public function getCollectionRateAttribute(): float
   126	    {
   127	        $total = $this->total_subscriptions_count;
   128	        if ($total === 0) return 0;
   129	
   130	        return round(($this->paid_count / $total) * 100, 2);
   131	    }
   132	
   133	    // ─── METHODS ──────────────────────────────────────────────────
   134	
   135	    public function calculateLateFee(float $baseAmount): float
   136	    {
   137	        if ($this->late_fee > 0) {
   138	            return $this->late_fee;
   139	        }
   140	
   141	        if ($this->late_fee_percentage > 0) {
   142	            return round($baseAmount * ($this->late_fee_percentage / 100), 2);
   143	        }
   144	
   145	        return 0;
   146	    }
   147	
   148	    // ─── ACTIVITY LOG ─────────────────────────────────────────────
   149	
   150	    public function getActivitylogOptions(): LogOptions
   151	    {
   152	        return LogOptions::defaults()
   153	            ->logAll()
   154	            ->logOnlyDirty()
   155	            ->useLogName('subscription_periods')
   156	            ->setDescriptionForEvent(fn (string $eventName) => "Subscription Period {$this->year} was {$eventName}");
   157	    }
   158	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [281]: app/Models/SubscriptionPeriod.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [282/494]: app/Models/SuspensionReason.php
│ LANGUAGE: php | LINES: 30 | SIZE: 621 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Models;
     4	
     5	use App\Models\Traits\HasActiveScope;
     6	use App\Models\Traits\HasBilingualName;
     7	use App\Models\Traits\HasDisplayOrder;
     8	use Illuminate\Database\Eloquent\Model;
     9	
    10	class SuspensionReason extends Model
    11	{
    12	    use HasActiveScope, HasBilingualName, HasDisplayOrder;
    13	
    14	    public $timestamps = false;
    15	
    16	    protected $fillable = [
    17	        'reason_key',
    18	        'name_ar',
    19	        'name_en',
    20	        'display_order',
    21	        'is_active',
    22	    ];
    23	
    24	    protected function casts(): array
    25	    {
    26	        return [
    27	            'is_active' => 'boolean',
    28	            'display_order' => 'integer',
    29	        ];
    30	    }
    31	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [282]: app/Models/SuspensionReason.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [283/494]: app/Models/SystemSetting.php
│ LANGUAGE: php | LINES: 56 | SIZE: 1534 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Models;
     4	
     5	use Illuminate\Database\Eloquent\Factories\HasFactory;
     6	use Illuminate\Database\Eloquent\Model;
     7	use Spatie\Activitylog\LogOptions;
     8	use Spatie\Activitylog\Traits\LogsActivity;
     9	
    10	class SystemSetting extends Model
    11	{
    12	    use HasFactory, LogsActivity;
    13	
    14	    protected $fillable = [
    15	        'setting_key',
    16	        'setting_value',
    17	        'value_type',
    18	        'group_key',
    19	        'sub_group',
    20	        'description_ar',
    21	        'description_en',
    22	        'display_order',
    23	    ];
    24	
    25	    protected $casts = [
    26	        'display_order' => 'integer',
    27	    ];
    28	
    29	    public function getActivitylogOptions(): LogOptions
    30	    {
    31	        return LogOptions::defaults()
    32	            ->logOnly(['setting_key', 'setting_value'])
    33	            ->logOnlyDirty()
    34	            ->useLogName('settings');
    35	    }
    36	
    37	    public function getCastedValueAttribute(): mixed
    38	    {
    39	        return match ($this->value_type) {
    40	            'boolean' => filter_var($this->setting_value, FILTER_VALIDATE_BOOLEAN),
    41	            'integer' => (int) $this->setting_value,
    42	            'float', 'decimal' => (float) $this->setting_value,
    43	            'json', 'array' => json_decode($this->setting_value, true),
    44	            default => $this->setting_value,
    45	        };
    46	    }
    47	
    48	    public function scopeForGroup($query, string $group)
    49	    {
    50	        return $query->where('group_key', $group);
    51	    }
    52	
    53	    public function scopeForSubGroup($query, string $group, string $subGroup)
    54	    {
    55	        return $query->where('group_key', $group)->where('sub_group', $subGroup);
    56	    }
    57	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [283]: app/Models/SystemSetting.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [284/494]: app/Models/Traits/Archivable.php
│ LANGUAGE: php | LINES: 49 | SIZE: 1065 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Models\Traits;
     4	
     5	use Illuminate\Database\Eloquent\Builder;
     6	
     7	trait Archivable
     8	{
     9	    public function initializeArchivable(): void
    10	    {
    11	        $this->mergeFillable(['archived_at', 'archived_by']);
    12	        $this->mergeCasts(['archived_at' => 'datetime']);
    13	    }
    14	
    15	    public function archive(?int $userId = null): bool
    16	    {
    17	        $this->archived_at = now();
    18	        $this->archived_by = $userId ?? auth()->id();
    19	
    20	        return $this->save();
    21	    }
    22	
    23	    public function unarchive(): bool
    24	    {
    25	        $this->archived_at = null;
    26	        $this->archived_by = null;
    27	
    28	        return $this->save();
    29	    }
    30	
    31	    public function isArchived(): bool
    32	    {
    33	        return $this->archived_at !== null;
    34	    }
    35	
    36	    public function scopeArchived(Builder $query): Builder
    37	    {
    38	        return $query->whereNotNull('archived_at');
    39	    }
    40	
    41	    public function scopeNotArchived(Builder $query): Builder
    42	    {
    43	        return $query->whereNull('archived_at');
    44	    }
    45	
    46	    public function scopeWithArchived(Builder $query): Builder
    47	    {
    48	        return $query;
    49	    }
    50	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [284]: app/Models/Traits/Archivable.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [285/494]: app/Models/Traits/HasActiveScope.php
│ LANGUAGE: php | LINES: 17 | SIZE: 344 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Models\Traits;
     4	
     5	use Illuminate\Database\Eloquent\Builder;
     6	
     7	trait HasActiveScope
     8	{
     9	    public function scopeActive(Builder $query): Builder
    10	    {
    11	        return $query->where('is_active', true);
    12	    }
    13	
    14	    public function scopeInactive(Builder $query): Builder
    15	    {
    16	        return $query->where('is_active', false);
    17	    }
    18	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [285]: app/Models/Traits/HasActiveScope.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [286/494]: app/Models/Traits/HasAuditTrail.php
│ LANGUAGE: php | LINES: 33 | SIZE: 973 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Models\Traits;
     4	
     5	use Spatie\Activitylog\LogOptions;
     6	use Spatie\Activitylog\Traits\LogsActivity;
     7	
     8	trait HasAuditTrail
     9	{
    10	    use LogsActivity;
    11	
    12	    public function getActivitylogOptions(): LogOptions
    13	    {
    14	        return LogOptions::defaults()
    15	            ->logAll()
    16	            ->logOnlyDirty()
    17	            ->dontSubmitEmptyLogs()
    18	            ->useLogName($this->getTable())
    19	            ->setDescriptionForEvent(function (string $eventName) {
    20	                $modelLabel = static::getAuditLabel();
    21	                return match ($eventName) {
    22	                    'created' => "تم إنشاء {$modelLabel}",
    23	                    'updated' => "تم تعديل {$modelLabel}",
    24	                    'deleted' => "تم حذف {$modelLabel}",
    25	                    default => "تم {$eventName} {$modelLabel}",
    26	                };
    27	            });
    28	    }
    29	
    30	    public static function getAuditLabel(): string
    31	    {
    32	        return class_basename(static::class);
    33	    }
    34	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [286]: app/Models/Traits/HasAuditTrail.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [287/494]: app/Models/Traits/HasBilingualName.php
│ LANGUAGE: php | LINES: 24 | SIZE: 648 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Models\Traits;
     4	
     5	use Illuminate\Database\Eloquent\Casts\Attribute;
     6	
     7	trait HasBilingualName
     8	{
     9	    public function displayName(): Attribute
    10	    {
    11	        return Attribute::make(
    12	            get: fn () => app()->getLocale() === 'ar'
    13	                ? ($this->name_ar ?? $this->name_en ?? '')
    14	                : ($this->name_en ?? $this->name_ar ?? ''),
    15	        );
    16	    }
    17	
    18	    public function scopeSearchByName($query, string $search)
    19	    {
    20	        return $query->where(function ($q) use ($search) {
    21	            $q->where('name_ar', 'like', "%{$search}%")
    22	              ->orWhere('name_en', 'like', "%{$search}%");
    23	        });
    24	    }
    25	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [287]: app/Models/Traits/HasBilingualName.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [288/494]: app/Models/Traits/HasDisplayOrder.php
│ LANGUAGE: php | LINES: 17 | SIZE: 385 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Models\Traits;
     4	
     5	use Illuminate\Database\Eloquent\Builder;
     6	
     7	trait HasDisplayOrder
     8	{
     9	    public function scopeOrdered(Builder $query, string $direction = 'asc'): Builder
    10	    {
    11	        return $query->orderBy('display_order', $direction);
    12	    }
    13	
    14	    public static function getNextDisplayOrder(): int
    15	    {
    16	        return (int) static::max('display_order') + 1;
    17	    }
    18	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [288]: app/Models/Traits/HasDisplayOrder.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [289/494]: app/Models/Traits/HasFiscalYear.php
│ LANGUAGE: php | LINES: 24 | SIZE: 621 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Models\Traits;
     4	
     5	use App\Models\FiscalYear;
     6	use Illuminate\Database\Eloquent\Builder;
     7	use Illuminate\Database\Eloquent\Relations\BelongsTo;
     8	
     9	trait HasFiscalYear
    10	{
    11	    public function fiscalYear(): BelongsTo
    12	    {
    13	        return $this->belongsTo(FiscalYear::class);
    14	    }
    15	
    16	    public function scopeForFiscalYear(Builder $query, int $fiscalYearId): Builder
    17	    {
    18	        return $query->where('fiscal_year_id', $fiscalYearId);
    19	    }
    20	
    21	    public function scopeCurrentFiscalYear(Builder $query): Builder
    22	    {
    23	        return $query->whereHas('fiscalYear', fn ($q) => $q->where('is_current', true));
    24	    }
    25	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [289]: app/Models/Traits/HasFiscalYear.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [290/494]: app/Models/Traits/HasMemberScope.php
│ LANGUAGE: php | LINES: 17 | SIZE: 394 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Models\Traits;
     4	
     5	use Illuminate\Database\Eloquent\Builder;
     6	
     7	trait HasMemberScope
     8	{
     9	    public function scopeForMember(Builder $query, int $memberId): Builder
    10	    {
    11	        return $query->where('member_id', $memberId);
    12	    }
    13	
    14	    public function scopeForMembers(Builder $query, array $memberIds): Builder
    15	    {
    16	        return $query->whereIn('member_id', $memberIds);
    17	    }
    18	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [290]: app/Models/Traits/HasMemberScope.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [291/494]: app/Models/Traits/HasNumberingSequence.php
│ LANGUAGE: php | LINES: 29 | SIZE: 869 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Models\Traits;
     4	
     5	use App\Models\NumberingSequence;
     6	use Illuminate\Support\Facades\DB;
     7	
     8	trait HasNumberingSequence
     9	{
    10	    public static function generateNumber(string $sequenceKey): string
    11	    {
    12	        return DB::transaction(function () use ($sequenceKey) {
    13	            $sequence = NumberingSequence::where('sequence_key', $sequenceKey)
    14	                ->where('is_active', true)
    15	                ->lockForUpdate()
    16	                ->firstOrFail();
    17	
    18	            $sequence->increment('current_value', $sequence->increment_by);
    19	            $sequence->refresh();
    20	
    21	            $number = $sequence->current_value;
    22	
    23	            if ($sequence->pad_length > 0) {
    24	                $number = str_pad((string) $number, $sequence->pad_length, '0', STR_PAD_LEFT);
    25	            }
    26	
    27	            return $sequence->prefix . $number . $sequence->suffix;
    28	        });
    29	    }
    30	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [291]: app/Models/Traits/HasNumberingSequence.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [292/494]: app/Models/Transfer.php
│ LANGUAGE: php | LINES: 87 | SIZE: 2267 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Models;
     4	
     5	use App\Casts\MoneyCast;
     6	use App\Enums\TransferStatus;
     7	use App\Enums\TransferType;
     8	use App\Models\Traits\HasAuditTrail;
     9	use App\Models\Traits\HasMemberScope;
    10	use App\Models\Traits\HasNumberingSequence;
    11	use Illuminate\Database\Eloquent\Model;
    12	use Illuminate\Database\Eloquent\Relations\BelongsTo;
    13	use Illuminate\Database\Eloquent\SoftDeletes;
    14	
    15	class Transfer extends Model
    16	{
    17	    use SoftDeletes, HasAuditTrail, HasMemberScope, HasNumberingSequence;
    18	
    19	    protected $fillable = [
    20	        'transfer_number',
    21	        'member_id',
    22	        'transfer_type',
    23	        'status',
    24	        'from_membership_type_id',
    25	        'to_membership_type_id',
    26	        'transfer_fee',
    27	        'fee_difference',
    28	        'receipt_id',
    29	        'reason',
    30	        'board_decision_reference',
    31	        'board_decision_date',
    32	        'requested_at',
    33	        'approved_at',
    34	        'approved_by',
    35	        'completed_at',
    36	        'completed_by',
    37	        'rejected_at',
    38	        'rejected_by',
    39	        'rejection_reason',
    40	        'notes',
    41	        'created_by',
    42	    ];
    43	
    44	    protected function casts(): array
    45	    {
    46	        return [
    47	            'transfer_type' => TransferType::class,
    48	            'status' => TransferStatus::class,
    49	            'transfer_fee' => MoneyCast::class,
    50	            'fee_difference' => MoneyCast::class,
    51	            'board_decision_date' => 'date',
    52	            'requested_at' => 'datetime',
    53	            'approved_at' => 'datetime',
    54	            'completed_at' => 'datetime',
    55	            'rejected_at' => 'datetime',
    56	        ];
    57	    }
    58	
    59	    public function member(): BelongsTo
    60	    {
    61	        return $this->belongsTo(Member::class);
    62	    }
    63	
    64	    public function fromMembershipType(): BelongsTo
    65	    {
    66	        return $this->belongsTo(MembershipType::class, 'from_membership_type_id');
    67	    }
    68	
    69	    public function toMembershipType(): BelongsTo
    70	    {
    71	        return $this->belongsTo(MembershipType::class, 'to_membership_type_id');
    72	    }
    73	
    74	    public function receipt(): BelongsTo
    75	    {
    76	        return $this->belongsTo(Receipt::class);
    77	    }
    78	
    79	    public function approvedByUser(): BelongsTo
    80	    {
    81	        return $this->belongsTo(User::class, 'approved_by');
    82	    }
    83	
    84	    public static function getAuditLabel(): string
    85	    {
    86	        return 'نقل عضوية';
    87	    }
    88	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [292]: app/Models/Transfer.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [293/494]: app/Models/User.php
│ LANGUAGE: php | LINES: 58 | SIZE: 1373 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Models;
     4	
     5	use App\Models\Traits\HasAuditTrail;
     6	use Filament\Models\Contracts\FilamentUser;
     7	use Filament\Models\Contracts\HasAvatar;
     8	use Filament\Panel;
     9	use Illuminate\Database\Eloquent\Factories\HasFactory;
    10	use Illuminate\Database\Eloquent\SoftDeletes;
    11	use Illuminate\Foundation\Auth\User as Authenticatable;
    12	use Illuminate\Notifications\Notifiable;
    13	use Spatie\Permission\Traits\HasRoles;
    14	
    15	class User extends Authenticatable implements FilamentUser, HasAvatar
    16	{
    17	    use HasFactory, Notifiable, HasRoles, SoftDeletes, HasAuditTrail;
    18	
    19	    protected $fillable = [
    20	        'name',
    21	        'email',
    22	        'password',
    23	        'avatar_url',
    24	        'is_active',
    25	        'language',
    26	        'last_login_at',
    27	        'last_login_ip',
    28	    ];
    29	
    30	    protected $hidden = [
    31	        'password',
    32	        'remember_token',
    33	    ];
    34	
    35	    protected function casts(): array
    36	    {
    37	        return [
    38	            'email_verified_at' => 'datetime',
    39	            'last_login_at' => 'datetime',
    40	            'password' => 'hashed',
    41	            'is_active' => 'boolean',
    42	        ];
    43	    }
    44	
    45	    public function canAccessPanel(Panel $panel): bool
    46	    {
    47	        return $this->is_active;
    48	    }
    49	
    50	    public function getFilamentAvatarUrl(): ?string
    51	    {
    52	        return $this->avatar_url;
    53	    }
    54	
    55	    public static function getAuditLabel(): string
    56	    {
    57	        return 'مستخدم';
    58	    }
    59	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [293]: app/Models/User.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [294/494]: app/Models/Violation.php
│ LANGUAGE: php | LINES: 109 | SIZE: 3135 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	declare(strict_types=1);
     4	
     5	namespace App\Models;
     6	
     7	use App\Enums\ViolationSeverity;
     8	use App\Enums\ViolationStatus;
     9	use Illuminate\Database\Eloquent\Factories\HasFactory;
    10	use Illuminate\Database\Eloquent\Model;
    11	use Illuminate\Database\Eloquent\Relations\BelongsTo;
    12	use Illuminate\Database\Eloquent\Relations\HasMany;
    13	use Illuminate\Database\Eloquent\SoftDeletes;
    14	use Spatie\Activitylog\LogOptions;
    15	use Spatie\Activitylog\Traits\LogsActivity;
    16	
    17	class Violation extends Model
    18	{
    19	    use HasFactory, SoftDeletes, LogsActivity;
    20	
    21	    protected $guarded = ['id'];
    22	
    23	    protected $casts = [
    24	        'severity'                   => ViolationSeverity::class,
    25	        'status'                     => ViolationStatus::class,
    26	        'violation_date'             => 'datetime',
    27	        'reported_at'                => 'datetime',
    28	        'investigation_started_at'   => 'datetime',
    29	        'investigation_completed_at' => 'datetime',
    30	        'escalation_date'            => 'datetime',
    31	        'board_decision_date'        => 'datetime',
    32	        'resolved_at'                => 'datetime',
    33	        'dismissed_at'               => 'datetime',
    34	        'escalated_to_board'         => 'boolean',
    35	    ];
    36	
    37	    public function getActivitylogOptions(): LogOptions
    38	    {
    39	        return LogOptions::defaults()
    40	            ->logAll()
    41	            ->logOnlyDirty()
    42	            ->dontSubmitEmptyLogs();
    43	    }
    44	
    45	    // ─── Relationships ──────────────────────────────────
    46	
    47	    public function member(): BelongsTo
    48	    {
    49	        return $this->belongsTo(Member::class);
    50	    }
    51	
    52	    public function violationType(): BelongsTo
    53	    {
    54	        return $this->belongsTo(ViolationType::class);
    55	    }
    56	
    57	    public function reporter(): BelongsTo
    58	    {
    59	        return $this->belongsTo(User::class, 'reported_by');
    60	    }
    61	
    62	    public function investigator(): BelongsTo
    63	    {
    64	        return $this->belongsTo(User::class, 'investigated_by');
    65	    }
    66	
    67	    public function dismissedByUser(): BelongsTo
    68	    {
    69	        return $this->belongsTo(User::class, 'dismissed_by');
    70	    }
    71	
    72	    public function penalties(): HasMany
    73	    {
    74	        return $this->hasMany(Penalty::class);
    75	    }
    76	
    77	    public function suspensions(): HasMany
    78	    {
    79	        return $this->hasMany(MemberSuspension::class);
    80	    }
    81	
    82	    public function documents(): HasMany
    83	    {
    84	        return $this->hasMany(ViolationDocument::class);
    85	    }
    86	
    87	    // ─── Scopes ─────────────────────────────────────────
    88	
    89	    public function scopeUnresolved($query)
    90	    {
    91	        return $query->whereNotIn('status', [
    92	            ViolationStatus::DISMISSED,
    93	            ViolationStatus::RESOLVED,
    94	            ViolationStatus::APPEALED_OVERTURNED,
    95	        ]);
    96	    }
    97	
    98	    public function scopeCritical($query)
    99	    {
   100	        return $query->whereIn('severity', [
   101	            ViolationSeverity::MAJOR,
   102	            ViolationSeverity::CRITICAL,
   103	        ]);
   104	    }
   105	
   106	    public function scopeForMember($query, int $memberId)
   107	    {
   108	        return $query->where('member_id', $memberId);
   109	    }
   110	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [294]: app/Models/Violation.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [295/494]: app/Models/ViolationDocument.php
│ LANGUAGE: php | LINES: 20 | SIZE: 408 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	declare(strict_types=1);
     4	
     5	namespace App\Models;
     6	
     7	use Illuminate\Database\Eloquent\Factories\HasFactory;
     8	use Illuminate\Database\Eloquent\Model;
     9	use Illuminate\Database\Eloquent\Relations\BelongsTo;
    10	
    11	class ViolationDocument extends Model
    12	{
    13	    use HasFactory;
    14	
    15	    protected $guarded = ['id'];
    16	
    17	    public function violation(): BelongsTo
    18	    {
    19	        return $this->belongsTo(Violation::class);
    20	    }
    21	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [295]: app/Models/ViolationDocument.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [296/494]: app/Models/ViolationType.php
│ LANGUAGE: php | LINES: 28 | SIZE: 668 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	declare(strict_types=1);
     4	
     5	namespace App\Models;
     6	
     7	use App\Enums\ViolationSeverity;
     8	use Illuminate\Database\Eloquent\Factories\HasFactory;
     9	use Illuminate\Database\Eloquent\Model;
    10	use Illuminate\Database\Eloquent\Relations\HasMany;
    11	
    12	class ViolationType extends Model
    13	{
    14	    use HasFactory;
    15	
    16	    protected $guarded = ['id'];
    17	
    18	    protected $casts = [
    19	        'default_severity'       => ViolationSeverity::class,
    20	        'requires_investigation' => 'boolean',
    21	        'requires_board_review'  => 'boolean',
    22	        'is_active'              => 'boolean',
    23	    ];
    24	
    25	    public function violations(): HasMany
    26	    {
    27	        return $this->hasMany(Violation::class);
    28	    }
    29	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [296]: app/Models/ViolationType.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [297/494]: app/Models/WorkflowProgress.php
│ LANGUAGE: php | LINES: 53 | SIZE: 1164 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Models;
     4	
     5	use App\Enums\WorkflowProgressStatus;
     6	use App\Models\Traits\HasAuditTrail;
     7	use Illuminate\Database\Eloquent\Model;
     8	use Illuminate\Database\Eloquent\Relations\BelongsTo;
     9	
    10	class WorkflowProgress extends Model
    11	{
    12	    use HasAuditTrail;
    13	
    14	    protected $table = 'workflow_progress';
    15	
    16	    protected $fillable = [
    17	        'member_id',
    18	        'workflow_stage_id',
    19	        'status',
    20	        'started_at',
    21	        'completed_at',
    22	        'completed_by',
    23	        'notes',
    24	    ];
    25	
    26	    protected function casts(): array
    27	    {
    28	        return [
    29	            'status' => WorkflowProgressStatus::class,
    30	            'started_at' => 'datetime',
    31	            'completed_at' => 'datetime',
    32	        ];
    33	    }
    34	
    35	    public function member(): BelongsTo
    36	    {
    37	        return $this->belongsTo(Member::class);
    38	    }
    39	
    40	    public function workflowStage(): BelongsTo
    41	    {
    42	        return $this->belongsTo(WorkflowStage::class);
    43	    }
    44	
    45	    public function completedByUser(): BelongsTo
    46	    {
    47	        return $this->belongsTo(User::class, 'completed_by');
    48	    }
    49	
    50	    public static function getAuditLabel(): string
    51	    {
    52	        return 'تقدم سير العمل';
    53	    }
    54	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [297]: app/Models/WorkflowProgress.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [298/494]: app/Models/WorkflowStage.php
│ LANGUAGE: php | LINES: 42 | SIZE: 973 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Models;
     4	
     5	use Illuminate\Database\Eloquent\Model;
     6	use Illuminate\Database\Eloquent\Relations\HasMany;
     7	
     8	class WorkflowStage extends Model
     9	{
    10	    public $timestamps = false;
    11	
    12	    protected $fillable = [
    13	        'stage_key',
    14	        'stage_order',
    15	        'name_ar',
    16	        'name_en',
    17	        'requires_payment',
    18	        'requires_documents',
    19	        'requires_approval',
    20	        'next_stage_order',
    21	    ];
    22	
    23	    protected function casts(): array
    24	    {
    25	        return [
    26	            'stage_order' => 'integer',
    27	            'requires_payment' => 'boolean',
    28	            'requires_documents' => 'boolean',
    29	            'requires_approval' => 'boolean',
    30	            'next_stage_order' => 'integer',
    31	        ];
    32	    }
    33	
    34	    public function workflowProgress(): HasMany
    35	    {
    36	        return $this->hasMany(WorkflowProgress::class);
    37	    }
    38	
    39	    public static function getByKey(string $key): ?static
    40	    {
    41	        return static::where('stage_key', $key)->first();
    42	    }
    43	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [298]: app/Models/WorkflowStage.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [299/494]: app/Observers/BoardOfferObserver.php
│ LANGUAGE: php | LINES: 34 | SIZE: 945 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Observers;
     4	
     5	use App\Models\BoardOffer;
     6	use App\Services\Admin\AuditService;
     7	
     8	class BoardOfferObserver
     9	{
    10	    protected AuditService $auditService;
    11	
    12	    public function __construct(AuditService $auditService)
    13	    {
    14	        $this->auditService = $auditService;
    15	    }
    16	
    17	    public function updating(BoardOffer $offer): void
    18	    {
    19	        if ($offer->isDirty('status')) {
    20	            $oldStatus = $offer->getOriginal('status');
    21	            $newStatus = $offer->status;
    22	
    23	            $this->auditService->log(
    24	                'board_offer_status_changed',
    25	                "تم تغيير حالة العرض {$offer->offer_number} من {$oldStatus?->getLabel()} إلى {$newStatus->getLabel()}",
    26	                $offer,
    27	                [
    28	                    'old_status' => $oldStatus?->value,
    29	                    'new_status' => $newStatus->value,
    30	                ],
    31	                'board_offers'
    32	            );
    33	        }
    34	    }
    35	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [299]: app/Observers/BoardOfferObserver.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [300/494]: app/Observers/CashRegisterObserver.php
│ LANGUAGE: php | LINES: 20 | SIZE: 513 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Observers;
     4	
     5	use App\Models\CashRegister;
     6	use App\Enums\CashRegisterStatus;
     7	
     8	class CashRegisterObserver
     9	{
    10	    public function updating(CashRegister $register): void
    11	    {
    12	        if ($register->isDirty('status') && $register->status === CashRegisterStatus::CLOSED) {
    13	            if (!$register->closed_at) {
    14	                $register->closed_at = now();
    15	            }
    16	            if (!$register->closed_by) {
    17	                $register->closed_by = auth()->id();
    18	            }
    19	        }
    20	    }
    21	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [300]: app/Observers/CashRegisterObserver.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [301/494]: app/Observers/DependentObserver.php
│ LANGUAGE: php | LINES: 46 | SIZE: 1184 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	declare(strict_types=1);
     4	
     5	namespace App\Observers;
     6	
     7	use App\Models\Dependent;
     8	use App\Enums\DependentStatus;
     9	use Illuminate\Support\Facades\Auth;
    10	
    11	final class DependentObserver
    12	{
    13	    public function creating(Dependent $dependent): void
    14	    {
    15	        if (!$dependent->status) {
    16	            $dependent->status = DependentStatus::PENDING_REVIEW;
    17	        }
    18	
    19	        if (!$dependent->created_by) {
    20	            $dependent->created_by = Auth::id();
    21	        }
    22	    }
    23	
    24	    public function updating(Dependent $dependent): void
    25	    {
    26	        if (!$dependent->updated_by) {
    27	            $dependent->updated_by = Auth::id();
    28	        }
    29	
    30	        // Track status changes
    31	        if ($dependent->isDirty('status')) {
    32	            $dependent->status_changed_at = now();
    33	        }
    34	    }
    35	
    36	    public function deleted(Dependent $dependent): void
    37	    {
    38	        activity('dependent')
    39	            ->performedOn($dependent)
    40	            ->causedBy(Auth::user())
    41	            ->withProperties([
    42	                'member_id' => $dependent->member_id,
    43	                'dependent_name' => $dependent->full_name_ar,
    44	            ])
    45	            ->log("تم حذف التابع: {$dependent->full_name_ar}");
    46	    }
    47	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [301]: app/Observers/DependentObserver.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [302/494]: app/Observers/MemberCardObserver.php
│ LANGUAGE: php | LINES: 37 | SIZE: 991 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Observers;
     4	
     5	use App\Enums\CardStatus;
     6	use App\Models\MemberCard;
     7	use Illuminate\Support\Facades\Auth;
     8	
     9	class MemberCardObserver
    10	{
    11	    public function creating(MemberCard $card): void
    12	    {
    13	        if (!$card->created_by) {
    14	            $card->created_by = Auth::id();
    15	        }
    16	        if (!$card->updated_by) {
    17	            $card->updated_by = Auth::id();
    18	        }
    19	    }
    20	
    21	    public function updating(MemberCard $card): void
    22	    {
    23	        $card->updated_by = Auth::id();
    24	    }
    25	
    26	    public function updated(MemberCard $card): void
    27	    {
    28	        // If card expired or cancelled, check if member needs notification
    29	        if ($card->wasChanged('status')) {
    30	            $newStatus = $card->status;
    31	
    32	            if (in_array($newStatus, [CardStatus::EXPIRED->value, 'expired'])) {
    33	                // Could trigger notification here via NotificationService
    34	                // App\Services\Notifications\NotificationService::cardExpired($card);
    35	            }
    36	        }
    37	    }
    38	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [302]: app/Observers/MemberCardObserver.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [303/494]: app/Observers/MemberDocumentObserver.php
│ LANGUAGE: php | LINES: 38 | SIZE: 1076 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Observers;
     4	
     5	use App\Models\MemberDocument;
     6	use Illuminate\Support\Facades\Auth;
     7	
     8	class MemberDocumentObserver
     9	{
    10	    public function creating(MemberDocument $document): void
    11	    {
    12	        if (!$document->created_by) {
    13	            $document->created_by = Auth::id();
    14	        }
    15	        if (!$document->updated_by) {
    16	            $document->updated_by = Auth::id();
    17	        }
    18	    }
    19	
    20	    public function updating(MemberDocument $document): void
    21	    {
    22	        $document->updated_by = Auth::id();
    23	    }
    24	
    25	    public function updated(MemberDocument $document): void
    26	    {
    27	        // If verification status changed, log it
    28	        if ($document->wasChanged('is_verified')) {
    29	            activity()
    30	                ->performedOn($document)
    31	                ->causedBy(Auth::user())
    32	                ->withProperties([
    33	                    'is_verified' => $document->is_verified,
    34	                    'verified_by' => $document->verified_by,
    35	                ])
    36	                ->log($document->is_verified ? 'document_verified' : 'document_unverified');
    37	        }
    38	    }
    39	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [303]: app/Observers/MemberDocumentObserver.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [304/494]: app/Observers/MemberObserver.php
│ LANGUAGE: php | LINES: 92 | SIZE: 2717 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Observers;
     4	
     5	use App\Enums\MembershipStatus;
     6	use App\Models\Member;
     7	use Illuminate\Support\Facades\Cache;
     8	
     9	class MemberObserver
    10	{
    11	    public function creating(Member $member): void
    12	    {
    13	        $member->created_by = $member->created_by ?? auth()->id();
    14	        $member->application_date = $member->application_date ?? now()->toDateString();
    15	    }
    16	
    17	    public function created(Member $member): void
    18	    {
    19	        $this->clearCaches();
    20	    }
    21	
    22	    public function updating(Member $member): void
    23	    {
    24	        $member->updated_by = auth()->id();
    25	
    26	        // Track status changes
    27	        if ($member->isDirty('membership_status')) {
    28	            $oldStatus = $member->getOriginal('membership_status');
    29	            $newStatus = $member->membership_status;
    30	
    31	            $member->last_status_change_at = now();
    32	            $member->last_status_change_from = $oldStatus;
    33	        }
    34	    }
    35	
    36	    public function updated(Member $member): void
    37	    {
    38	        $this->clearCaches();
    39	
    40	        // Log status transitions
    41	        if ($member->wasChanged('membership_status')) {
    42	            $from = $member->getOriginal('membership_status');
    43	            $to = $member->membership_status;
    44	
    45	            activity()
    46	                ->performedOn($member)
    47	                ->causedBy(auth()->user())
    48	                ->withProperties([
    49	                    'from' => $from instanceof MembershipStatus ? $from->value : $from,
    50	                    'to' => $to instanceof MembershipStatus ? $to->value : $to,
    51	                ])
    52	                ->log("تغيير حالة العضوية: {$from?->value} → {$to?->value}");
    53	        }
    54	    }
    55	
    56	    public function deleted(Member $member): void
    57	    {
    58	        $this->clearCaches();
    59	
    60	        activity()
    61	            ->performedOn($member)
    62	            ->causedBy(auth()->user())
    63	            ->log('تم حذف ملف العضو (soft delete)');
    64	    }
    65	
    66	    public function restored(Member $member): void
    67	    {
    68	        $this->clearCaches();
    69	
    70	        activity()
    71	            ->performedOn($member)
    72	            ->causedBy(auth()->user())
    73	            ->log('تم استعادة ملف العضو');
    74	    }
    75	
    76	    public function forceDeleted(Member $member): void
    77	    {
    78	        $this->clearCaches();
    79	
    80	        activity()
    81	            ->causedBy(auth()->user())
    82	            ->withProperties(['member_name' => $member->full_name_ar, 'national_id' => $member->national_id])
    83	            ->log("تم حذف ملف العضو نهائياً: {$member->full_name_ar}");
    84	    }
    85	
    86	    private function clearCaches(): void
    87	    {
    88	        Cache::forget('members_count');
    89	        Cache::forget('active_members_count');
    90	        Cache::forget('pending_members_count');
    91	        Cache::forget('dashboard_stats');
    92	    }
    93	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [304]: app/Observers/MemberObserver.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [305/494]: app/Observers/MemberSuspensionObserver.php
│ LANGUAGE: php | LINES: 21 | SIZE: 453 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	declare(strict_types=1);
     4	
     5	namespace App\Observers;
     6	
     7	use App\Models\MemberSuspension;
     8	use Illuminate\Support\Facades\Auth;
     9	
    10	class MemberSuspensionObserver
    11	{
    12	    public function creating(MemberSuspension $suspension): void
    13	    {
    14	        if (empty($suspension->suspended_by)) {
    15	            $suspension->suspended_by = Auth::id();
    16	        }
    17	
    18	        if (empty($suspension->start_date)) {
    19	            $suspension->start_date = now();
    20	        }
    21	    }
    22	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [305]: app/Observers/MemberSuspensionObserver.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [306/494]: app/Observers/PenaltyObserver.php
│ LANGUAGE: php | LINES: 25 | SIZE: 532 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	declare(strict_types=1);
     4	
     5	namespace App\Observers;
     6	
     7	use App\Models\Penalty;
     8	use Illuminate\Support\Facades\Auth;
     9	
    10	class PenaltyObserver
    11	{
    12	    public function creating(Penalty $penalty): void
    13	    {
    14	        if (empty($penalty->imposed_by)) {
    15	            $penalty->imposed_by = Auth::id();
    16	        }
    17	
    18	        if (empty($penalty->imposed_date)) {
    19	            $penalty->imposed_date = now();
    20	        }
    21	
    22	        if (empty($penalty->effective_from)) {
    23	            $penalty->effective_from = $penalty->imposed_date;
    24	        }
    25	    }
    26	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [306]: app/Observers/PenaltyObserver.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [307/494]: app/Observers/ReceiptObserver.php
│ LANGUAGE: php | LINES: 65 | SIZE: 2180 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Observers;
     4	
     5	use App\Models\Receipt;
     6	use App\Models\CashRegisterTransaction;
     7	use App\Enums\ReceiptStatus;
     8	
     9	class ReceiptObserver
    10	{
    11	    public function created(Receipt $receipt): void
    12	    {
    13	        if ($receipt->cash_register_id && $receipt->status === ReceiptStatus::Paid) {
    14	            $this->recordCashTransaction($receipt);
    15	        }
    16	    }
    17	
    18	    public function updated(Receipt $receipt): void
    19	    {
    20	        if ($receipt->isDirty('status') && $receipt->status === ReceiptStatus::Cancelled) {
    21	            if ($receipt->cash_register_id) {
    22	                $this->recordCashReversal($receipt);
    23	            }
    24	        }
    25	    }
    26	
    27	    protected function recordCashTransaction(Receipt $receipt): void
    28	    {
    29	        $register = $receipt->cashRegister;
    30	        if (!$register) return;
    31	
    32	        $balanceBefore = $register->current_balance;
    33	        $register->increment('current_balance', $receipt->total_amount);
    34	
    35	        CashRegisterTransaction::create([
    36	            'cash_register_id' => $register->id,
    37	            'receipt_id' => $receipt->id,
    38	            'transaction_type' => 'receipt',
    39	            'amount' => $receipt->total_amount,
    40	            'balance_before' => $balanceBefore,
    41	            'balance_after' => $balanceBefore + $receipt->total_amount,
    42	            'description' => "إيصال رقم {$receipt->receipt_number}",
    43	            'created_by' => auth()->id(),
    44	        ]);
    45	    }
    46	
    47	    protected function recordCashReversal(Receipt $receipt): void
    48	    {
    49	        $register = $receipt->cashRegister;
    50	        if (!$register) return;
    51	
    52	        $balanceBefore = $register->current_balance;
    53	        $register->decrement('current_balance', $receipt->total_amount);
    54	
    55	        CashRegisterTransaction::create([
    56	            'cash_register_id' => $register->id,
    57	            'receipt_id' => $receipt->id,
    58	            'transaction_type' => 'reversal',
    59	            'amount' => -$receipt->total_amount,
    60	            'balance_before' => $balanceBefore,
    61	            'balance_after' => $balanceBefore - $receipt->total_amount,
    62	            'description' => "إلغاء إيصال رقم {$receipt->receipt_number}",
    63	            'created_by' => auth()->id(),
    64	        ]);
    65	    }
    66	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [307]: app/Observers/ReceiptObserver.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [308/494]: app/Observers/SubscriptionObserver.php
│ LANGUAGE: php | LINES: 67 | SIZE: 2328 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Observers;
     4	
     5	use App\Enums\SubscriptionStatus;
     6	use App\Models\Subscription;
     7	use Illuminate\Support\Facades\Auth;
     8	
     9	class SubscriptionObserver
    10	{
    11	    public function creating(Subscription $subscription): void
    12	    {
    13	        $subscription->created_by = $subscription->created_by ?? Auth::id();
    14	
    15	        // Auto-calculate net and balance if not set
    16	        if (!$subscription->net_amount) {
    17	            $total = ($subscription->base_amount ?? 0) + ($subscription->dependents_amount ?? 0);
    18	            $subscription->total_amount = $total;
    19	            $subscription->net_amount = $total - ($subscription->discount_amount ?? 0) + ($subscription->late_fee_amount ?? 0);
    20	            $subscription->balance = $subscription->net_amount - ($subscription->paid_amount ?? 0);
    21	        }
    22	    }
    23	
    24	    public function updating(Subscription $subscription): void
    25	    {
    26	        $subscription->updated_by = Auth::id();
    27	    }
    28	
    29	    public function updated(Subscription $subscription): void
    30	    {
    31	        // If subscription just became PAID, update member subscription status
    32	        if ($subscription->wasChanged('status') && $subscription->status === SubscriptionStatus::PAID) {
    33	            $this->updateMemberSubscriptionCurrency($subscription);
    34	        }
    35	
    36	        // If subscription became OVERDUE, flag the member
    37	        if ($subscription->wasChanged('status') && $subscription->status === SubscriptionStatus::OVERDUE) {
    38	            $this->flagMemberOverdue($subscription);
    39	        }
    40	    }
    41	
    42	    protected function updateMemberSubscriptionCurrency(Subscription $subscription): void
    43	    {
    44	        $member = $subscription->member;
    45	
    46	        if (!$member) return;
    47	
    48	        // Check if this is for the current period
    49	        $period = $subscription->subscriptionPeriod;
    50	        if ($period && $period->is_current) {
    51	            // Member has paid current subscription — ensure they're not flagged
    52	            if (method_exists($member, 'markSubscriptionCurrent')) {
    53	                $member->markSubscriptionCurrent();
    54	            }
    55	        }
    56	    }
    57	
    58	    protected function flagMemberOverdue(Subscription $subscription): void
    59	    {
    60	        $member = $subscription->member;
    61	
    62	        if (!$member) return;
    63	
    64	        if (method_exists($member, 'markSubscriptionOverdue')) {
    65	            $member->markSubscriptionOverdue();
    66	        }
    67	    }
    68	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [308]: app/Observers/SubscriptionObserver.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [309/494]: app/Observers/ViolationObserver.php
│ LANGUAGE: php | LINES: 32 | SIZE: 859 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	declare(strict_types=1);
     4	
     5	namespace App\Observers;
     6	
     7	use App\Models\Violation;
     8	use Illuminate\Support\Facades\Auth;
     9	
    10	class ViolationObserver
    11	{
    12	    public function creating(Violation $violation): void
    13	    {
    14	        if (empty($violation->reported_by)) {
    15	            $violation->reported_by = Auth::id();
    16	        }
    17	
    18	        if (empty($violation->reported_at)) {
    19	            $violation->reported_at = now();
    20	        }
    21	    }
    22	
    23	    public function updating(Violation $violation): void
    24	    {
    25	        // Track resolution timestamp
    26	        if ($violation->isDirty('status')) {
    27	            $resolvedStatuses = ['dismissed', 'resolved', 'appealed_overturned'];
    28	            if (in_array($violation->status->value ?? $violation->status, $resolvedStatuses) && !$violation->resolved_at) {
    29	                $violation->resolved_at = now();
    30	            }
    31	        }
    32	    }
    33	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [309]: app/Observers/ViolationObserver.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [310/494]: app/Policies/BoardDecisionPolicy.php
│ LANGUAGE: php | LINES: 36 | SIZE: 821 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Policies;
     4	
     5	use App\Models\User;
     6	use App\Models\BoardDecision;
     7	use Illuminate\Auth\Access\HandlesAuthorization;
     8	
     9	class BoardDecisionPolicy
    10	{
    11	    use HandlesAuthorization;
    12	
    13	    public function viewAny(User $user): bool
    14	    {
    15	        return $user->can('view_any_board::decision');
    16	    }
    17	
    18	    public function view(User $user, BoardDecision $decision): bool
    19	    {
    20	        return $user->can('view_board::decision');
    21	    }
    22	
    23	    public function create(User $user): bool
    24	    {
    25	        return $user->can('create_board::decision');
    26	    }
    27	
    28	    public function update(User $user, BoardDecision $decision): bool
    29	    {
    30	        return $user->can('update_board::decision');
    31	    }
    32	
    33	    public function delete(User $user, BoardDecision $decision): bool
    34	    {
    35	        return $user->can('delete_board::decision');
    36	    }
    37	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [310]: app/Policies/BoardDecisionPolicy.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [311/494]: app/Policies/BoardOfferPolicy.php
│ LANGUAGE: php | LINES: 44 | SIZE: 930 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Policies;
     4	
     5	use App\Models\User;
     6	use App\Models\BoardOffer;
     7	use Illuminate\Auth\Access\HandlesAuthorization;
     8	
     9	class BoardOfferPolicy
    10	{
    11	    use HandlesAuthorization;
    12	
    13	    public function viewAny(User $user): bool
    14	    {
    15	        return $user->can('view_any_board::offer');
    16	    }
    17	
    18	    public function view(User $user, BoardOffer $offer): bool
    19	    {
    20	        return $user->can('view_board::offer');
    21	    }
    22	
    23	    public function create(User $user): bool
    24	    {
    25	        return $user->can('create_board::offer');
    26	    }
    27	
    28	    public function update(User $user, BoardOffer $offer): bool
    29	    {
    30	        if ($offer->isFinalized()) {
    31	            return false;
    32	        }
    33	
    34	        return $user->can('update_board::offer');
    35	    }
    36	
    37	    public function delete(User $user, BoardOffer $offer): bool
    38	    {
    39	        if ($offer->isFinalized()) {
    40	            return false;
    41	        }
    42	
    43	        return $user->can('delete_board::offer');
    44	    }
    45	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [311]: app/Policies/BoardOfferPolicy.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [312/494]: app/Policies/CardPolicy.php
│ LANGUAGE: php | LINES: 38 | SIZE: 727 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Policies;
     4	
     5	use App\Models\Card;
     6	use App\Models\User;
     7	
     8	class CardPolicy
     9	{
    10	    public function viewAny(User $user): bool
    11	    {
    12	        return $user->can('view_any_card');
    13	    }
    14	
    15	    public function view(User $user, Card $card): bool
    16	    {
    17	        return $user->can('view_card');
    18	    }
    19	
    20	    public function create(User $user): bool
    21	    {
    22	        return $user->can('create_card');
    23	    }
    24	
    25	    public function update(User $user, Card $card): bool
    26	    {
    27	        return $user->can('update_card');
    28	    }
    29	
    30	    public function delete(User $user, Card $card): bool
    31	    {
    32	        return $user->can('delete_card');
    33	    }
    34	
    35	    public function print(User $user): bool
    36	    {
    37	        return $user->can('print_card');
    38	    }
    39	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [312]: app/Policies/CardPolicy.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [313/494]: app/Policies/CashRegisterPolicy.php
│ LANGUAGE: php | LINES: 40 | SIZE: 883 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Policies;
     4	
     5	use App\Models\User;
     6	use App\Models\CashRegister;
     7	use Illuminate\Auth\Access\HandlesAuthorization;
     8	
     9	class CashRegisterPolicy
    10	{
    11	    use HandlesAuthorization;
    12	
    13	    public function viewAny(User $user): bool
    14	    {
    15	        return $user->can('view_any_cash::register');
    16	    }
    17	
    18	    public function view(User $user, CashRegister $register): bool
    19	    {
    20	        return $user->can('view_cash::register');
    21	    }
    22	
    23	    public function create(User $user): bool
    24	    {
    25	        return $user->can('create_cash::register');
    26	    }
    27	
    28	    public function update(User $user, CashRegister $register): bool
    29	    {
    30	        return $user->can('update_cash::register');
    31	    }
    32	
    33	    public function delete(User $user, CashRegister $register): bool
    34	    {
    35	        if ($register->isOpen()) {
    36	            return false;
    37	        }
    38	
    39	        return $user->can('delete_cash::register');
    40	    }
    41	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [313]: app/Policies/CashRegisterPolicy.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [314/494]: app/Policies/DependentPolicy.php
│ LANGUAGE: php | LINES: 104 | SIZE: 2783 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	declare(strict_types=1);
     4	
     5	namespace App\Policies;
     6	
     7	use App\Models\Dependent;
     8	use App\Models\User;
     9	use App\Enums\DependentStatus;
    10	use Illuminate\Auth\Access\HandlesAuthorization;
    11	
    12	final class DependentPolicy
    13	{
    14	    use HandlesAuthorization;
    15	
    16	    public function viewAny(User $user): bool
    17	    {
    18	        return $user->can('view_any_dependent');
    19	    }
    20	
    21	    public function view(User $user, Dependent $dependent): bool
    22	    {
    23	        return $user->can('view_dependent');
    24	    }
    25	
    26	    public function create(User $user): bool
    27	    {
    28	        return $user->can('create_dependent');
    29	    }
    30	
    31	    public function update(User $user, Dependent $dependent): bool
    32	    {
    33	        if ($dependent->is_archived) {
    34	            return false;
    35	        }
    36	
    37	        return $user->can('update_dependent');
    38	    }
    39	
    40	    public function delete(User $user, Dependent $dependent): bool
    41	    {
    42	        return $user->can('delete_dependent');
    43	    }
    44	
    45	    public function approve(User $user, Dependent $dependent): bool
    46	    {
    47	        return $user->can('approve_dependent')
    48	            && in_array($dependent->status, [DependentStatus::PENDING_REVIEW, DependentStatus::PENDING_DOCUMENTS]);
    49	    }
    50	
    51	    public function reject(User $user, Dependent $dependent): bool
    52	    {
    53	        return $user->can('reject_dependent')
    54	            && in_array($dependent->status, [DependentStatus::PENDING_REVIEW, DependentStatus::PENDING_DOCUMENTS]);
    55	    }
    56	
    57	    public function suspend(User $user, Dependent $dependent): bool
    58	    {
    59	        return $user->can('suspend_dependent')
    60	            && $dependent->status === DependentStatus::ACTIVE;
    61	    }
    62	
    63	    public function reactivate(User $user, Dependent $dependent): bool
    64	    {
    65	        return $user->can('reactivate_dependent')
    66	            && $dependent->status === DependentStatus::SUSPENDED;
    67	    }
    68	
    69	    public function archive(User $user, Dependent $dependent): bool
    70	    {
    71	        return $user->can('archive_dependent')
    72	            && !$dependent->is_archived;
    73	    }
    74	
    75	    public function requestDocuments(User $user, Dependent $dependent): bool
    76	    {
    77	        return $user->can('approve_dependent')
    78	            && $dependent->status === DependentStatus::PENDING_REVIEW;
    79	    }
    80	
    81	    public function deleteAny(User $user): bool
    82	    {
    83	        return $user->can('delete_any_dependent');
    84	    }
    85	
    86	    public function forceDelete(User $user, Dependent $dependent): bool
    87	    {
    88	        return $user->can('force_delete_dependent');
    89	    }
    90	
    91	    public function forceDeleteAny(User $user): bool
    92	    {
    93	        return $user->can('force_delete_any_dependent');
    94	    }
    95	
    96	    public function restore(User $user, Dependent $dependent): bool
    97	    {
    98	        return $user->can('restore_dependent');
    99	    }
   100	
   101	    public function restoreAny(User $user): bool
   102	    {
   103	        return $user->can('restore_any_dependent');
   104	    }
   105	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [314]: app/Policies/DependentPolicy.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [315/494]: app/Policies/DocumentRequirementPolicy.php
│ LANGUAGE: php | LINES: 71 | SIZE: 1914 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Policies;
     4	
     5	use App\Models\DocumentRequirement;
     6	use App\Models\User;
     7	use Illuminate\Auth\Access\HandlesAuthorization;
     8	
     9	class DocumentRequirementPolicy
    10	{
    11	    use HandlesAuthorization;
    12	
    13	    public function viewAny(User $user): bool
    14	    {
    15	        return $user->can('view_any_document::requirement');
    16	    }
    17	
    18	    public function view(User $user, DocumentRequirement $documentRequirement): bool
    19	    {
    20	        return $user->can('view_document::requirement');
    21	    }
    22	
    23	    public function create(User $user): bool
    24	    {
    25	        return $user->can('create_document::requirement');
    26	    }
    27	
    28	    public function update(User $user, DocumentRequirement $documentRequirement): bool
    29	    {
    30	        return $user->can('update_document::requirement');
    31	    }
    32	
    33	    public function delete(User $user, DocumentRequirement $documentRequirement): bool
    34	    {
    35	        return $user->can('delete_document::requirement');
    36	    }
    37	
    38	    public function deleteAny(User $user): bool
    39	    {
    40	        return $user->can('delete_any_document::requirement');
    41	    }
    42	
    43	    public function forceDelete(User $user, DocumentRequirement $documentRequirement): bool
    44	    {
    45	        return $user->can('force_delete_document::requirement');
    46	    }
    47	
    48	    public function forceDeleteAny(User $user): bool
    49	    {
    50	        return $user->can('force_delete_any_document::requirement');
    51	    }
    52	
    53	    public function restore(User $user, DocumentRequirement $documentRequirement): bool
    54	    {
    55	        return $user->can('restore_document::requirement');
    56	    }
    57	
    58	    public function restoreAny(User $user): bool
    59	    {
    60	        return $user->can('restore_any_document::requirement');
    61	    }
    62	
    63	    public function replicate(User $user, DocumentRequirement $documentRequirement): bool
    64	    {
    65	        return $user->can('replicate_document::requirement');
    66	    }
    67	
    68	    public function reorder(User $user): bool
    69	    {
    70	        return $user->can('reorder_document::requirement');
    71	    }
    72	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [315]: app/Policies/DocumentRequirementPolicy.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [316/494]: app/Policies/DocumentTypePolicy.php
│ LANGUAGE: php | LINES: 71 | SIZE: 1732 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Policies;
     4	
     5	use App\Models\DocumentType;
     6	use App\Models\User;
     7	use Illuminate\Auth\Access\HandlesAuthorization;
     8	
     9	class DocumentTypePolicy
    10	{
    11	    use HandlesAuthorization;
    12	
    13	    public function viewAny(User $user): bool
    14	    {
    15	        return $user->can('view_any_document::type');
    16	    }
    17	
    18	    public function view(User $user, DocumentType $documentType): bool
    19	    {
    20	        return $user->can('view_document::type');
    21	    }
    22	
    23	    public function create(User $user): bool
    24	    {
    25	        return $user->can('create_document::type');
    26	    }
    27	
    28	    public function update(User $user, DocumentType $documentType): bool
    29	    {
    30	        return $user->can('update_document::type');
    31	    }
    32	
    33	    public function delete(User $user, DocumentType $documentType): bool
    34	    {
    35	        return $user->can('delete_document::type');
    36	    }
    37	
    38	    public function deleteAny(User $user): bool
    39	    {
    40	        return $user->can('delete_any_document::type');
    41	    }
    42	
    43	    public function forceDelete(User $user, DocumentType $documentType): bool
    44	    {
    45	        return $user->can('force_delete_document::type');
    46	    }
    47	
    48	    public function forceDeleteAny(User $user): bool
    49	    {
    50	        return $user->can('force_delete_any_document::type');
    51	    }
    52	
    53	    public function restore(User $user, DocumentType $documentType): bool
    54	    {
    55	        return $user->can('restore_document::type');
    56	    }
    57	
    58	    public function restoreAny(User $user): bool
    59	    {
    60	        return $user->can('restore_any_document::type');
    61	    }
    62	
    63	    public function replicate(User $user, DocumentType $documentType): bool
    64	    {
    65	        return $user->can('replicate_document::type');
    66	    }
    67	
    68	    public function reorder(User $user): bool
    69	    {
    70	        return $user->can('reorder_document::type');
    71	    }
    72	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [316]: app/Policies/DocumentTypePolicy.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [317/494]: app/Policies/MemberCardPolicy.php
│ LANGUAGE: php | LINES: 71 | SIZE: 1680 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Policies;
     4	
     5	use App\Models\MemberCard;
     6	use App\Models\User;
     7	use Illuminate\Auth\Access\HandlesAuthorization;
     8	
     9	class MemberCardPolicy
    10	{
    11	    use HandlesAuthorization;
    12	
    13	    public function viewAny(User $user): bool
    14	    {
    15	        return $user->can('view_any_member::card');
    16	    }
    17	
    18	    public function view(User $user, MemberCard $memberCard): bool
    19	    {
    20	        return $user->can('view_member::card');
    21	    }
    22	
    23	    public function create(User $user): bool
    24	    {
    25	        return $user->can('create_member::card');
    26	    }
    27	
    28	    public function update(User $user, MemberCard $memberCard): bool
    29	    {
    30	        return $user->can('update_member::card');
    31	    }
    32	
    33	    public function delete(User $user, MemberCard $memberCard): bool
    34	    {
    35	        return $user->can('delete_member::card');
    36	    }
    37	
    38	    public function deleteAny(User $user): bool
    39	    {
    40	        return $user->can('delete_any_member::card');
    41	    }
    42	
    43	    public function forceDelete(User $user, MemberCard $memberCard): bool
    44	    {
    45	        return $user->can('force_delete_member::card');
    46	    }
    47	
    48	    public function forceDeleteAny(User $user): bool
    49	    {
    50	        return $user->can('force_delete_any_member::card');
    51	    }
    52	
    53	    public function restore(User $user, MemberCard $memberCard): bool
    54	    {
    55	        return $user->can('restore_member::card');
    56	    }
    57	
    58	    public function restoreAny(User $user): bool
    59	    {
    60	        return $user->can('restore_any_member::card');
    61	    }
    62	
    63	    public function replicate(User $user, MemberCard $memberCard): bool
    64	    {
    65	        return $user->can('replicate_member::card');
    66	    }
    67	
    68	    public function reorder(User $user): bool
    69	    {
    70	        return $user->can('reorder_member::card');
    71	    }
    72	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [317]: app/Policies/MemberCardPolicy.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [318/494]: app/Policies/MemberDocumentPolicy.php
│ LANGUAGE: php | LINES: 71 | SIZE: 1784 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Policies;
     4	
     5	use App\Models\MemberDocument;
     6	use App\Models\User;
     7	use Illuminate\Auth\Access\HandlesAuthorization;
     8	
     9	class MemberDocumentPolicy
    10	{
    11	    use HandlesAuthorization;
    12	
    13	    public function viewAny(User $user): bool
    14	    {
    15	        return $user->can('view_any_member::document');
    16	    }
    17	
    18	    public function view(User $user, MemberDocument $memberDocument): bool
    19	    {
    20	        return $user->can('view_member::document');
    21	    }
    22	
    23	    public function create(User $user): bool
    24	    {
    25	        return $user->can('create_member::document');
    26	    }
    27	
    28	    public function update(User $user, MemberDocument $memberDocument): bool
    29	    {
    30	        return $user->can('update_member::document');
    31	    }
    32	
    33	    public function delete(User $user, MemberDocument $memberDocument): bool
    34	    {
    35	        return $user->can('delete_member::document');
    36	    }
    37	
    38	    public function deleteAny(User $user): bool
    39	    {
    40	        return $user->can('delete_any_member::document');
    41	    }
    42	
    43	    public function forceDelete(User $user, MemberDocument $memberDocument): bool
    44	    {
    45	        return $user->can('force_delete_member::document');
    46	    }
    47	
    48	    public function forceDeleteAny(User $user): bool
    49	    {
    50	        return $user->can('force_delete_any_member::document');
    51	    }
    52	
    53	    public function restore(User $user, MemberDocument $memberDocument): bool
    54	    {
    55	        return $user->can('restore_member::document');
    56	    }
    57	
    58	    public function restoreAny(User $user): bool
    59	    {
    60	        return $user->can('restore_any_member::document');
    61	    }
    62	
    63	    public function replicate(User $user, MemberDocument $memberDocument): bool
    64	    {
    65	        return $user->can('replicate_member::document');
    66	    }
    67	
    68	    public function reorder(User $user): bool
    69	    {
    70	        return $user->can('reorder_member::document');
    71	    }
    72	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [318]: app/Policies/MemberDocumentPolicy.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [319/494]: app/Policies/MemberPolicy.php
│ LANGUAGE: php | LINES: 97 | SIZE: 2318 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Policies;
     4	
     5	use App\Models\Member;
     6	use App\Models\User;
     7	use Illuminate\Auth\Access\HandlesAuthorization;
     8	
     9	class MemberPolicy
    10	{
    11	    use HandlesAuthorization;
    12	
    13	    public function viewAny(User $user): bool
    14	    {
    15	        return $user->can('view_any_member');
    16	    }
    17	
    18	    public function view(User $user, Member $member): bool
    19	    {
    20	        return $user->can('view_member');
    21	    }
    22	
    23	    public function create(User $user): bool
    24	    {
    25	        return $user->can('create_member');
    26	    }
    27	
    28	    public function update(User $user, Member $member): bool
    29	    {
    30	        return $user->can('update_member');
    31	    }
    32	
    33	    public function delete(User $user, Member $member): bool
    34	    {
    35	        return $user->can('delete_member');
    36	    }
    37	
    38	    public function deleteAny(User $user): bool
    39	    {
    40	        return $user->can('delete_any_member');
    41	    }
    42	
    43	    public function forceDelete(User $user, Member $member): bool
    44	    {
    45	        return $user->can('force_delete_member');
    46	    }
    47	
    48	    public function forceDeleteAny(User $user): bool
    49	    {
    50	        return $user->can('force_delete_any_member');
    51	    }
    52	
    53	    public function restore(User $user, Member $member): bool
    54	    {
    55	        return $user->can('restore_member');
    56	    }
    57	
    58	    public function restoreAny(User $user): bool
    59	    {
    60	        return $user->can('restore_any_member');
    61	    }
    62	
    63	    public function replicate(User $user, Member $member): bool
    64	    {
    65	        return false; // Members should never be replicated
    66	    }
    67	
    68	    // Custom permissions for workflow actions
    69	    public function advanceWorkflow(User $user, Member $member): bool
    70	    {
    71	        return $user->can('advance_workflow_member');
    72	    }
    73	
    74	    public function activateMembership(User $user, Member $member): bool
    75	    {
    76	        return $user->can('activate_member');
    77	    }
    78	
    79	    public function suspendMembership(User $user, Member $member): bool
    80	    {
    81	        return $user->can('suspend_member');
    82	    }
    83	
    84	    public function cancelMembership(User $user, Member $member): bool
    85	    {
    86	        return $user->can('cancel_member');
    87	    }
    88	
    89	    public function recordInterview(User $user, Member $member): bool
    90	    {
    91	        return $user->can('record_interview_member');
    92	    }
    93	
    94	    public function recordBoardDecision(User $user, Member $member): bool
    95	    {
    96	        return $user->can('record_board_decision_member');
    97	    }
    98	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [319]: app/Policies/MemberPolicy.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [320/494]: app/Policies/MemberSuspensionPolicy.php
│ LANGUAGE: php | LINES: 48 | SIZE: 1167 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	declare(strict_types=1);
     4	
     5	namespace App\Policies;
     6	
     7	use App\Models\MemberSuspension;
     8	use App\Models\User;
     9	use Illuminate\Auth\Access\HandlesAuthorization;
    10	
    11	class MemberSuspensionPolicy
    12	{
    13	    use HandlesAuthorization;
    14	
    15	    public function viewAny(User $user): bool
    16	    {
    17	        return $user->can('view_any_member::suspension');
    18	    }
    19	
    20	    public function view(User $user, MemberSuspension $suspension): bool
    21	    {
    22	        return $user->can('view_member::suspension');
    23	    }
    24	
    25	    public function create(User $user): bool
    26	    {
    27	        return $user->can('create_member::suspension');
    28	    }
    29	
    30	    public function update(User $user, MemberSuspension $suspension): bool
    31	    {
    32	        return $user->can('update_member::suspension');
    33	    }
    34	
    35	    public function delete(User $user, MemberSuspension $suspension): bool
    36	    {
    37	        return $user->can('delete_member::suspension');
    38	    }
    39	
    40	    public function lift(User $user, MemberSuspension $suspension): bool
    41	    {
    42	        return $user->can('lift_member::suspension');
    43	    }
    44	
    45	    public function extend(User $user, MemberSuspension $suspension): bool
    46	    {
    47	        return $user->can('extend_member::suspension');
    48	    }
    49	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [320]: app/Policies/MemberSuspensionPolicy.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [321/494]: app/Policies/NotificationPolicy.php
│ LANGUAGE: php | LINES: 31 | SIZE: 677 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Policies;
     4	
     5	use App\Models\User;
     6	use App\Models\Notification;
     7	use Illuminate\Auth\Access\HandlesAuthorization;
     8	
     9	class NotificationPolicy
    10	{
    11	    use HandlesAuthorization;
    12	
    13	    public function viewAny(User $user): bool
    14	    {
    15	        return $user->can('view_any_notification');
    16	    }
    17	
    18	    public function view(User $user, Notification $notification): bool
    19	    {
    20	        return $user->can('view_notification');
    21	    }
    22	
    23	    public function create(User $user): bool
    24	    {
    25	        return $user->can('create_notification');
    26	    }
    27	
    28	    public function delete(User $user, Notification $notification): bool
    29	    {
    30	        return $user->can('delete_notification');
    31	    }
    32	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [321]: app/Policies/NotificationPolicy.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [322/494]: app/Policies/PenaltyPolicy.php
│ LANGUAGE: php | LINES: 48 | SIZE: 1018 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	declare(strict_types=1);
     4	
     5	namespace App\Policies;
     6	
     7	use App\Models\Penalty;
     8	use App\Models\User;
     9	use Illuminate\Auth\Access\HandlesAuthorization;
    10	
    11	class PenaltyPolicy
    12	{
    13	    use HandlesAuthorization;
    14	
    15	    public function viewAny(User $user): bool
    16	    {
    17	        return $user->can('view_any_penalty');
    18	    }
    19	
    20	    public function view(User $user, Penalty $penalty): bool
    21	    {
    22	        return $user->can('view_penalty');
    23	    }
    24	
    25	    public function create(User $user): bool
    26	    {
    27	        return $user->can('create_penalty');
    28	    }
    29	
    30	    public function update(User $user, Penalty $penalty): bool
    31	    {
    32	        return $user->can('update_penalty');
    33	    }
    34	
    35	    public function delete(User $user, Penalty $penalty): bool
    36	    {
    37	        return $user->can('delete_penalty');
    38	    }
    39	
    40	    public function waive(User $user, Penalty $penalty): bool
    41	    {
    42	        return $user->can('waive_penalty');
    43	    }
    44	
    45	    public function overturn(User $user, Penalty $penalty): bool
    46	    {
    47	        return $user->can('overturn_penalty');
    48	    }
    49	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [322]: app/Policies/PenaltyPolicy.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [323/494]: app/Policies/PenaltyTypePolicy.php
│ LANGUAGE: php | LINES: 38 | SIZE: 836 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	declare(strict_types=1);
     4	
     5	namespace App\Policies;
     6	
     7	use App\Models\PenaltyType;
     8	use App\Models\User;
     9	use Illuminate\Auth\Access\HandlesAuthorization;
    10	
    11	class PenaltyTypePolicy
    12	{
    13	    use HandlesAuthorization;
    14	
    15	    public function viewAny(User $user): bool
    16	    {
    17	        return $user->can('view_any_penalty::type');
    18	    }
    19	
    20	    public function view(User $user, PenaltyType $penaltyType): bool
    21	    {
    22	        return $user->can('view_penalty::type');
    23	    }
    24	
    25	    public function create(User $user): bool
    26	    {
    27	        return $user->can('create_penalty::type');
    28	    }
    29	
    30	    public function update(User $user, PenaltyType $penaltyType): bool
    31	    {
    32	        return $user->can('update_penalty::type');
    33	    }
    34	
    35	    public function delete(User $user, PenaltyType $penaltyType): bool
    36	    {
    37	        return $user->can('delete_penalty::type');
    38	    }
    39	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [323]: app/Policies/PenaltyTypePolicy.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [324/494]: app/Policies/ReceiptPolicy.php
│ LANGUAGE: php | LINES: 43 | SIZE: 972 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Policies;
     4	
     5	use App\Models\Receipt;
     6	use App\Models\User;
     7	
     8	class ReceiptPolicy
     9	{
    10	    public function viewAny(User $user): bool
    11	    {
    12	        return $user->can('view_any_receipt');
    13	    }
    14	
    15	    public function view(User $user, Receipt $receipt): bool
    16	    {
    17	        return $user->can('view_receipt');
    18	    }
    19	
    20	    public function create(User $user): bool
    21	    {
    22	        return $user->can('create_receipt');
    23	    }
    24	
    25	    public function update(User $user, Receipt $receipt): bool
    26	    {
    27	        return $user->can('update_receipt') && !$receipt->status->isFinal();
    28	    }
    29	
    30	    public function delete(User $user, Receipt $receipt): bool
    31	    {
    32	        return $user->can('delete_receipt') && !$receipt->status->isFinal();
    33	    }
    34	
    35	    public function cancel(User $user, Receipt $receipt): bool
    36	    {
    37	        return $user->can('cancel_receipt');
    38	    }
    39	
    40	    public function print(User $user, Receipt $receipt): bool
    41	    {
    42	        return $user->can('print_receipt');
    43	    }
    44	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [324]: app/Policies/ReceiptPolicy.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [325/494]: app/Policies/SequencePolicy.php
│ LANGUAGE: php | LINES: 26 | SIZE: 533 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Policies;
     4	
     5	use App\Models\User;
     6	use App\Models\Sequence;
     7	use Illuminate\Auth\Access\HandlesAuthorization;
     8	
     9	class SequencePolicy
    10	{
    11	    use HandlesAuthorization;
    12	
    13	    public function viewAny(User $user): bool
    14	    {
    15	        return $user->can('view_any_sequence');
    16	    }
    17	
    18	    public function view(User $user, Sequence $sequence): bool
    19	    {
    20	        return $user->can('view_sequence');
    21	    }
    22	
    23	    public function update(User $user, Sequence $sequence): bool
    24	    {
    25	        return $user->can('update_sequence');
    26	    }
    27	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [325]: app/Policies/SequencePolicy.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [326/494]: app/Policies/SubscriptionPeriodPolicy.php
│ LANGUAGE: php | LINES: 59 | SIZE: 1371 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Policies;
     4	
     5	use App\Models\SubscriptionPeriod;
     6	use App\Models\User;
     7	use Illuminate\Auth\Access\HandlesAuthorization;
     8	
     9	class SubscriptionPeriodPolicy
    10	{
    11	    use HandlesAuthorization;
    12	
    13	    public function viewAny(User $user): bool
    14	    {
    15	        return $user->can('subscriptions.view');
    16	    }
    17	
    18	    public function view(User $user, SubscriptionPeriod $period): bool
    19	    {
    20	        return $user->can('subscriptions.view');
    21	    }
    22	
    23	    public function create(User $user): bool
    24	    {
    25	        return $user->can('subscriptions.create');
    26	    }
    27	
    28	    public function update(User $user, SubscriptionPeriod $period): bool
    29	    {
    30	        if ($period->is_locked) {
    31	            return false;
    32	        }
    33	
    34	        return $user->can('subscriptions.edit');
    35	    }
    36	
    37	    public function delete(User $user, SubscriptionPeriod $period): bool
    38	    {
    39	        if ($period->is_locked) {
    40	            return false;
    41	        }
    42	
    43	        // Can't delete if subscriptions exist
    44	        if ($period->subscriptions()->exists()) {
    45	            return false;
    46	        }
    47	
    48	        return $user->can('subscriptions.delete');
    49	    }
    50	
    51	    public function restore(User $user, SubscriptionPeriod $period): bool
    52	    {
    53	        return $user->can('subscriptions.edit');
    54	    }
    55	
    56	    public function forceDelete(User $user, SubscriptionPeriod $period): bool
    57	    {
    58	        return false; // Never force delete
    59	    }
    60	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [326]: app/Policies/SubscriptionPeriodPolicy.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [327/494]: app/Policies/SubscriptionPolicy.php
│ LANGUAGE: php | LINES: 65 | SIZE: 1635 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Policies;
     4	
     5	use App\Models\Subscription;
     6	use App\Models\User;
     7	use Illuminate\Auth\Access\HandlesAuthorization;
     8	
     9	class SubscriptionPolicy
    10	{
    11	    use HandlesAuthorization;
    12	
    13	    public function viewAny(User $user): bool
    14	    {
    15	        return $user->can('subscriptions.view');
    16	    }
    17	
    18	    public function view(User $user, Subscription $subscription): bool
    19	    {
    20	        return $user->can('subscriptions.view');
    21	    }
    22	
    23	    public function create(User $user): bool
    24	    {
    25	        return $user->can('subscriptions.create');
    26	    }
    27	
    28	    public function update(User $user, Subscription $subscription): bool
    29	    {
    30	        if ($subscription->status->isTerminal()) {
    31	            return false;
    32	        }
    33	
    34	        return $user->can('subscriptions.edit');
    35	    }
    36	
    37	    public function delete(User $user, Subscription $subscription): bool
    38	    {
    39	        // Only allow deleting if nothing's been paid
    40	        if ($subscription->paid_amount > 0) {
    41	            return false;
    42	        }
    43	
    44	        return $user->can('subscriptions.delete');
    45	    }
    46	
    47	    public function waive(User $user, Subscription $subscription): bool
    48	    {
    49	        return $user->can('subscriptions.edit') && !$subscription->status->isTerminal();
    50	    }
    51	
    52	    public function recordPayment(User $user, Subscription $subscription): bool
    53	    {
    54	        return $user->can('receipts.create') && $subscription->status->isPayable();
    55	    }
    56	
    57	    public function reversePayment(User $user, Subscription $subscription): bool
    58	    {
    59	        return $user->can('receipts.void');
    60	    }
    61	
    62	    public function export(User $user): bool
    63	    {
    64	        return $user->can('subscriptions.export');
    65	    }
    66	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [327]: app/Policies/SubscriptionPolicy.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [328/494]: app/Policies/SystemSettingPolicy.php
│ LANGUAGE: php | LINES: 36 | SIZE: 818 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Policies;
     4	
     5	use App\Models\User;
     6	use App\Models\SystemSetting;
     7	use Illuminate\Auth\Access\HandlesAuthorization;
     8	
     9	class SystemSettingPolicy
    10	{
    11	    use HandlesAuthorization;
    12	
    13	    public function viewAny(User $user): bool
    14	    {
    15	        return $user->can('view_any_system::setting');
    16	    }
    17	
    18	    public function view(User $user, SystemSetting $setting): bool
    19	    {
    20	        return $user->can('view_system::setting');
    21	    }
    22	
    23	    public function create(User $user): bool
    24	    {
    25	        return $user->can('create_system::setting');
    26	    }
    27	
    28	    public function update(User $user, SystemSetting $setting): bool
    29	    {
    30	        return $user->can('update_system::setting');
    31	    }
    32	
    33	    public function delete(User $user, SystemSetting $setting): bool
    34	    {
    35	        return $user->can('delete_system::setting');
    36	    }
    37	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [328]: app/Policies/SystemSettingPolicy.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [329/494]: app/Policies/TransferPolicy.php
│ LANGUAGE: php | LINES: 33 | SIZE: 663 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Policies;
     4	
     5	use App\Models\Transfer;
     6	use App\Models\User;
     7	
     8	class TransferPolicy
     9	{
    10	    public function viewAny(User $user): bool
    11	    {
    12	        return $user->can('view_any_transfer');
    13	    }
    14	
    15	    public function view(User $user, Transfer $transfer): bool
    16	    {
    17	        return $user->can('view_transfer');
    18	    }
    19	
    20	    public function create(User $user): bool
    21	    {
    22	        return $user->can('create_transfer');
    23	    }
    24	
    25	    public function update(User $user, Transfer $transfer): bool
    26	    {
    27	        return $user->can('update_transfer');
    28	    }
    29	
    30	    public function approve(User $user): bool
    31	    {
    32	        return $user->can('approve_transfer');
    33	    }
    34	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [329]: app/Policies/TransferPolicy.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [330/494]: app/Policies/UserPolicy.php
│ LANGUAGE: php | LINES: 53 | SIZE: 1144 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Policies;
     4	
     5	use App\Models\User;
     6	use Illuminate\Auth\Access\HandlesAuthorization;
     7	
     8	class UserPolicy
     9	{
    10	    use HandlesAuthorization;
    11	
    12	    public function viewAny(User $user): bool
    13	    {
    14	        return $user->can('view_any_user');
    15	    }
    16	
    17	    public function view(User $user, User $model): bool
    18	    {
    19	        return $user->can('view_user');
    20	    }
    21	
    22	    public function create(User $user): bool
    23	    {
    24	        return $user->can('create_user');
    25	    }
    26	
    27	    public function update(User $user, User $model): bool
    28	    {
    29	        if ($model->id === $user->id) {
    30	            return true; // Users can always edit themselves
    31	        }
    32	
    33	        return $user->can('update_user');
    34	    }
    35	
    36	    public function delete(User $user, User $model): bool
    37	    {
    38	        if ($model->id === $user->id) {
    39	            return false; // Can't delete yourself
    40	        }
    41	
    42	        return $user->can('delete_user');
    43	    }
    44	
    45	    public function restore(User $user, User $model): bool
    46	    {
    47	        return $user->can('restore_user');
    48	    }
    49	
    50	    public function forceDelete(User $user, User $model): bool
    51	    {
    52	        return $user->can('force_delete_user');
    53	    }
    54	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [330]: app/Policies/UserPolicy.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [331/494]: app/Policies/ViolationPolicy.php
│ LANGUAGE: php | LINES: 58 | SIZE: 1329 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	declare(strict_types=1);
     4	
     5	namespace App\Policies;
     6	
     7	use App\Models\User;
     8	use App\Models\Violation;
     9	use Illuminate\Auth\Access\HandlesAuthorization;
    10	
    11	class ViolationPolicy
    12	{
    13	    use HandlesAuthorization;
    14	
    15	    public function viewAny(User $user): bool
    16	    {
    17	        return $user->can('view_any_violation');
    18	    }
    19	
    20	    public function view(User $user, Violation $violation): bool
    21	    {
    22	        return $user->can('view_violation');
    23	    }
    24	
    25	    public function create(User $user): bool
    26	    {
    27	        return $user->can('create_violation');
    28	    }
    29	
    30	    public function update(User $user, Violation $violation): bool
    31	    {
    32	        return $user->can('update_violation');
    33	    }
    34	
    35	    public function delete(User $user, Violation $violation): bool
    36	    {
    37	        return $user->can('delete_violation');
    38	    }
    39	
    40	    public function investigate(User $user, Violation $violation): bool
    41	    {
    42	        return $user->can('investigate_violation');
    43	    }
    44	
    45	    public function escalate(User $user, Violation $violation): bool
    46	    {
    47	        return $user->can('escalate_violation');
    48	    }
    49	
    50	    public function dismiss(User $user, Violation $violation): bool
    51	    {
    52	        return $user->can('dismiss_violation');
    53	    }
    54	
    55	    public function imposePenalty(User $user, Violation $violation): bool
    56	    {
    57	        return $user->can('impose_penalty');
    58	    }
    59	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [331]: app/Policies/ViolationPolicy.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [332/494]: app/Policies/ViolationTypePolicy.php
│ LANGUAGE: php | LINES: 38 | SIZE: 862 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	declare(strict_types=1);
     4	
     5	namespace App\Policies;
     6	
     7	use App\Models\User;
     8	use App\Models\ViolationType;
     9	use Illuminate\Auth\Access\HandlesAuthorization;
    10	
    11	class ViolationTypePolicy
    12	{
    13	    use HandlesAuthorization;
    14	
    15	    public function viewAny(User $user): bool
    16	    {
    17	        return $user->can('view_any_violation::type');
    18	    }
    19	
    20	    public function view(User $user, ViolationType $violationType): bool
    21	    {
    22	        return $user->can('view_violation::type');
    23	    }
    24	
    25	    public function create(User $user): bool
    26	    {
    27	        return $user->can('create_violation::type');
    28	    }
    29	
    30	    public function update(User $user, ViolationType $violationType): bool
    31	    {
    32	        return $user->can('update_violation::type');
    33	    }
    34	
    35	    public function delete(User $user, ViolationType $violationType): bool
    36	    {
    37	        return $user->can('delete_violation::type');
    38	    }
    39	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [332]: app/Policies/ViolationTypePolicy.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [333/494]: app/Providers/AdminServiceProvider.php
│ LANGUAGE: php | LINES: 35 | SIZE: 1154 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Providers;
     4	
     5	use Illuminate\Support\ServiceProvider;
     6	use App\Models\BoardOffer;
     7	use App\Models\CashRegister;
     8	use App\Observers\BoardOfferObserver;
     9	use App\Observers\CashRegisterObserver;
    10	use App\Services\Admin\SettingsService;
    11	use App\Services\Admin\SequenceService;
    12	use App\Services\Admin\AuditService;
    13	use App\Services\Admin\BoardService;
    14	use App\Services\Admin\CashRegisterService;
    15	use App\Services\Admin\BackupService;
    16	use App\Services\Admin\NotificationDispatchService;
    17	
    18	class AdminServiceProvider extends ServiceProvider
    19	{
    20	    public function register(): void
    21	    {
    22	        $this->app->singleton(SettingsService::class);
    23	        $this->app->singleton(SequenceService::class);
    24	        $this->app->singleton(AuditService::class);
    25	        $this->app->singleton(BoardService::class);
    26	        $this->app->singleton(CashRegisterService::class);
    27	        $this->app->singleton(BackupService::class);
    28	        $this->app->singleton(NotificationDispatchService::class);
    29	    }
    30	
    31	    public function boot(): void
    32	    {
    33	        BoardOffer::observe(BoardOfferObserver::class);
    34	        CashRegister::observe(CashRegisterObserver::class);
    35	    }
    36	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [333]: app/Providers/AdminServiceProvider.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [334/494]: app/Providers/AppServiceProvider.php
│ LANGUAGE: php | LINES: 45 | SIZE: 1514 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Providers;
     4	
     5	use App\Models\Member;
     6	use App\Models\Receipt;
     7	use App\Observers\MemberObserver;
     8	use App\Observers\ReceiptObserver;
     9	use App\Services\Financial\FeeCalculationService;
    10	use App\Services\Financial\NumberingService;
    11	use App\Services\Financial\ReceiptService;
    12	use App\Services\Membership\MembershipService;
    13	use App\Services\Disciplinary\ViolationService;
    14	use App\Services\Notifications\NotificationService;
    15	use App\Services\Reporting\ReportService;
    16	use Illuminate\Database\Eloquent\Model;
    17	use Illuminate\Support\Facades\URL;
    18	use Illuminate\Support\ServiceProvider;
    19	
    20	class AppServiceProvider extends ServiceProvider
    21	{
    22	    public function register(): void
    23	    {
    24	        $this->app->singleton(MembershipService::class);
    25	        $this->app->singleton(FeeCalculationService::class);
    26	        $this->app->singleton(ReceiptService::class);
    27	        $this->app->singleton(NumberingService::class);
    28	        $this->app->singleton(ViolationService::class);
    29	        $this->app->singleton(NotificationService::class);
    30	        $this->app->singleton(ReportService::class);
    31	    }
    32	
    33	    public function boot(): void
    34	    {
    35	        Model::unguard(false);
    36	        Model::preventLazyLoading(!$this->app->isProduction());
    37	        Model::preventSilentlyDiscardingAttributes(!$this->app->isProduction());
    38	
    39	        Member::observe(MemberObserver::class);
    40	        Receipt::observe(ReceiptObserver::class);
    41	
    42	        if ($this->app->environment('production')) {
    43	            URL::forceScheme('https');
    44	        }
    45	    }
    46	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [334]: app/Providers/AppServiceProvider.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [335/494]: app/Providers/AuthServiceProvider.php
│ LANGUAGE: php | LINES: 33 | SIZE: 915 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Providers;
     4	
     5	use App\Models\Card;
     6	use App\Models\Dependent;
     7	use App\Models\Member;
     8	use App\Models\Receipt;
     9	use App\Models\Transfer;
    10	use App\Models\Violation;
    11	use App\Policies\CardPolicy;
    12	use App\Policies\DependentPolicy;
    13	use App\Policies\MemberPolicy;
    14	use App\Policies\ReceiptPolicy;
    15	use App\Policies\TransferPolicy;
    16	use App\Policies\ViolationPolicy;
    17	use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;
    18	
    19	class AuthServiceProvider extends ServiceProvider
    20	{
    21	    protected $policies = [
    22	        Member::class => MemberPolicy::class,
    23	        Dependent::class => DependentPolicy::class,
    24	        Receipt::class => ReceiptPolicy::class,
    25	        Violation::class => ViolationPolicy::class,
    26	        Card::class => CardPolicy::class,
    27	        Transfer::class => TransferPolicy::class,
    28	    ];
    29	
    30	    public function boot(): void
    31	    {
    32	        $this->registerPolicies();
    33	    }
    34	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [335]: app/Providers/AuthServiceProvider.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [336/494]: app/Providers/CardsAndDocumentsServiceProvider.php
│ LANGUAGE: php | LINES: 23 | SIZE: 417 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	declare(strict_types=1);
     4	
     5	namespace App\Providers;
     6	
     7	use Illuminate\Support\ServiceProvider;
     8	
     9	/**
    10	 * Cards & Documents module service provider.
    11	 * (Previously named Phase5ServiceProvider — renamed for production clarity.)
    12	 */
    13	class CardsAndDocumentsServiceProvider extends ServiceProvider
    14	{
    15	    public function register(): void
    16	    {
    17	        //
    18	    }
    19	
    20	    public function boot(): void
    21	    {
    22	        //
    23	    }
    24	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [336]: app/Providers/CardsAndDocumentsServiceProvider.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [337/494]: app/Providers/DisciplinaryServiceProvider.php
│ LANGUAGE: php | LINES: 32 | SIZE: 937 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	declare(strict_types=1);
     4	
     5	namespace App\Providers;
     6	
     7	use App\Models\MemberSuspension;
     8	use App\Models\Penalty;
     9	use App\Models\Violation;
    10	use App\Observers\MemberSuspensionObserver;
    11	use App\Observers\PenaltyObserver;
    12	use App\Observers\ViolationObserver;
    13	use App\Services\Disciplinary\PenaltyService;
    14	use App\Services\Disciplinary\SuspensionService;
    15	use App\Services\Disciplinary\ViolationService;
    16	use Illuminate\Support\ServiceProvider;
    17	
    18	class DisciplinaryServiceProvider extends ServiceProvider
    19	{
    20	    public function register(): void
    21	    {
    22	        $this->app->singleton(SuspensionService::class);
    23	        $this->app->singleton(PenaltyService::class);
    24	        $this->app->singleton(ViolationService::class);
    25	    }
    26	
    27	    public function boot(): void
    28	    {
    29	        Violation::observe(ViolationObserver::class);
    30	        Penalty::observe(PenaltyObserver::class);
    31	        MemberSuspension::observe(MemberSuspensionObserver::class);
    32	    }
    33	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [337]: app/Providers/DisciplinaryServiceProvider.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [338/494]: app/Providers/EventServiceProvider.php
│ LANGUAGE: php | LINES: 19 | SIZE: 342 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Providers;
     4	
     5	use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider;
     6	
     7	class EventServiceProvider extends ServiceProvider
     8	{
     9	    protected $listen = [];
    10	
    11	    public function boot(): void
    12	    {
    13	        //
    14	    }
    15	
    16	    public function shouldDiscoverEvents(): bool
    17	    {
    18	        return false;
    19	    }
    20	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [338]: app/Providers/EventServiceProvider.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [339/494]: app/Providers/Filament/AdminPanelProvider.php
│ LANGUAGE: php | LINES: 131 | SIZE: 5176 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Providers\Filament;
     4	
     5	use Filament\Http\Middleware\Authenticate;
     6	use Filament\Http\Middleware\DisableBladeIconComponents;
     7	use Filament\Http\Middleware\DispatchServingFilamentEvent;
     8	use Filament\Navigation\NavigationGroup;
     9	use Filament\Pages;
    10	use Filament\Panel;
    11	use Filament\PanelProvider;
    12	use Filament\Support\Colors\Color;
    13	use Filament\Widgets;
    14	use Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse;
    15	use Illuminate\Cookie\Middleware\EncryptCookies;
    16	use Illuminate\Foundation\Http\Middleware\VerifyCsrfToken;
    17	use Illuminate\Routing\Middleware\SubstituteBindings;
    18	use Illuminate\Session\Middleware\AuthenticateSession;
    19	use Illuminate\Session\Middleware\StartSession;
    20	use Illuminate\View\Middleware\ShareErrorsFromSession;
    21	use BezhanSalleh\FilamentShield\FilamentShieldPlugin;
    22	
    23	class AdminPanelProvider extends PanelProvider
    24	{
    25	    public function panel(Panel $panel): Panel
    26	    {
    27	        return $panel
    28	            ->default()
    29	            ->id('admin')
    30	            ->path('admin')
    31	            ->login()
    32	            ->brandName(config('club.name_ar', 'نادي الآركيد'))
    33	            ->brandLogo(null)
    34	            ->favicon(null)
    35	            ->colors([
    36	                'primary' => Color::hex('#1a5276'),
    37	                'danger' => Color::Rose,
    38	                'gray' => Color::Slate,
    39	                'info' => Color::Blue,
    40	                'success' => Color::Emerald,
    41	                'warning' => Color::Orange,
    42	            ])
    43	            ->font('Cairo')
    44	            ->darkMode(true)
    45	            ->sidebarCollapsibleOnDesktop()
    46	            ->sidebarFullyCollapsibleOnDesktop()
    47	            ->maxContentWidth('full')
    48	            ->navigationGroups([
    49	                NavigationGroup::make()
    50	                    ->label('إدارة الأعضاء')
    51	                    ->icon('heroicon-o-users')
    52	                    ->collapsed(false),
    53	                NavigationGroup::make()
    54	                    ->label('التابعين')
    55	                    ->icon('heroicon-o-user-group')
    56	                    ->collapsed(true),
    57	                NavigationGroup::make()
    58	                    ->label('الشؤون المالية')
    59	                    ->icon('heroicon-o-banknotes')
    60	                    ->collapsed(true),
    61	                NavigationGroup::make()
    62	                    ->label('الاشتراكات')
    63	                    ->icon('heroicon-o-calendar')
    64	                    ->collapsed(true),
    65	                NavigationGroup::make()
    66	                    ->label('الكروت والمستندات')
    67	                    ->icon('heroicon-o-identification')
    68	                    ->collapsed(true),
    69	                NavigationGroup::make()
    70	                    ->label('المخالفات والجزاءات')
    71	                    ->icon('heroicon-o-shield-exclamation')
    72	                    ->collapsed(true),
    73	                NavigationGroup::make()
    74	                    ->label('مجلس الإدارة')
    75	                    ->icon('heroicon-o-building-library')
    76	                    ->collapsed(true),
    77	                NavigationGroup::make()
    78	                    ->label('إدارة النظام')
    79	                    ->icon('heroicon-o-cog-6-tooth')
    80	                    ->collapsed(true),
    81	                NavigationGroup::make()
    82	                    ->label('التقارير')
    83	                    ->icon('heroicon-o-chart-bar')
    84	                    ->collapsed(true),
    85	            ])
    86	            ->discoverResources(in: app_path('Filament/Resources'), for: 'App\\Filament\\Resources')
    87	            ->discoverPages(in: app_path('Filament/Pages'), for: 'App\\Filament\\Pages')
    88	            ->pages([
    89	                Pages\Dashboard::class,
    90	            ])
    91	            ->discoverWidgets(in: app_path('Filament/Widgets'), for: 'App\\Filament\\Widgets')
    92	            ->widgets([
    93	                Widgets\AccountWidget::class,
    94	            ])
    95	            ->middleware([
    96	                EncryptCookies::class,
    97	                AddQueuedCookiesToResponse::class,
    98	                StartSession::class,
    99	                AuthenticateSession::class,
   100	                ShareErrorsFromSession::class,
   101	                VerifyCsrfToken::class,
   102	                SubstituteBindings::class,
   103	                DisableBladeIconComponents::class,
   104	                DispatchServingFilamentEvent::class,
   105	            ])
   106	            ->authMiddleware([
   107	                Authenticate::class,
   108	            ])
   109	            ->plugins([
   110	                FilamentShieldPlugin::make()
   111	                    ->gridColumns([
   112	                        'default' => 1,
   113	                        'sm' => 2,
   114	                        'lg' => 3,
   115	                    ])
   116	                    ->sectionColumnSpan(1)
   117	                    ->checkboxListColumns([
   118	                        'default' => 1,
   119	                        'sm' => 2,
   120	                        'lg' => 3,
   121	                    ])
   122	                    ->resourceCheckboxListColumns([
   123	                        'default' => 1,
   124	                        'sm' => 2,
   125	                    ]),
   126	            ])
   127	            ->databaseNotifications()
   128	            ->databaseNotificationsPolling('60s')
   129	            ->spa()
   130	            ->viteTheme('resources/css/filament/admin/theme.css');
   131	    }
   132	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [339]: app/Providers/Filament/AdminPanelProvider.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [340/494]: app/Providers/FinancialServiceProvider.php
│ LANGUAGE: php | LINES: 45 | SIZE: 1393 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Providers;
     4	
     5	use App\Services\Financial\CashRegisterService;
     6	use App\Services\Financial\FeeCalculationService;
     7	use App\Services\Financial\InstallmentService;
     8	use App\Services\Financial\ReceiptService;
     9	use App\Services\Financial\SequenceService;
    10	use App\Services\Financial\SubscriptionService;
    11	use Illuminate\Support\ServiceProvider;
    12	
    13	class FinancialServiceProvider extends ServiceProvider
    14	{
    15	    public function register(): void
    16	    {
    17	        $this->app->singleton(SequenceService::class);
    18	        $this->app->singleton(FeeCalculationService::class);
    19	        $this->app->singleton(CashRegisterService::class);
    20	
    21	        $this->app->singleton(ReceiptService::class, function ($app) {
    22	            return new ReceiptService(
    23	                $app->make(SequenceService::class),
    24	                $app->make(CashRegisterService::class),
    25	            );
    26	        });
    27	
    28	        $this->app->singleton(SubscriptionService::class, function ($app) {
    29	            return new SubscriptionService(
    30	                $app->make(FeeCalculationService::class),
    31	                $app->make(ReceiptService::class),
    32	            );
    33	        });
    34	
    35	        $this->app->singleton(InstallmentService::class, function ($app) {
    36	            return new InstallmentService(
    37	                $app->make(ReceiptService::class),
    38	            );
    39	        });
    40	    }
    41	
    42	    public function boot(): void
    43	    {
    44	        //
    45	    }
    46	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [340]: app/Providers/FinancialServiceProvider.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [341/494]: app/Providers/MemberServiceProvider.php
│ LANGUAGE: php | LINES: 43 | SIZE: 1350 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Providers;
     4	
     5	use App\Models\Member;
     6	use App\Observers\MemberObserver;
     7	use App\Services\Membership\MemberNumberService;
     8	use App\Services\Membership\MembershipWorkflowService;
     9	use Illuminate\Console\Scheduling\Schedule;
    10	use Illuminate\Support\ServiceProvider;
    11	
    12	class MemberServiceProvider extends ServiceProvider
    13	{
    14	    public function register(): void
    15	    {
    16	        $this->app->singleton(MemberNumberService::class);
    17	        $this->app->singleton(MembershipWorkflowService::class);
    18	    }
    19	
    20	    public function boot(): void
    21	    {
    22	        Member::observe(MemberObserver::class);
    23	
    24	        // Register scheduled commands
    25	        $this->app->booted(function () {
    26	            /** @var Schedule $schedule */
    27	            $schedule = $this->app->make(Schedule::class);
    28	
    29	            $schedule->command('members:expire-overdue')
    30	                ->dailyAt('00:30')
    31	                ->withoutOverlapping()
    32	                ->appendOutputTo(storage_path('logs/expire-overdue.log'));
    33	
    34	            $schedule->command('members:payment-reminders')
    35	                ->dailyAt('09:00')
    36	                ->withoutOverlapping()
    37	                ->appendOutputTo(storage_path('logs/payment-reminders.log'));
    38	
    39	            $schedule->command('members:cache-stats')
    40	                ->everyThirtyMinutes()
    41	                ->withoutOverlapping();
    42	        });
    43	    }
    44	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [341]: app/Providers/MemberServiceProvider.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [342/494]: app/Providers/Phase5ServiceProvider.php
│ LANGUAGE: php | LINES: 23 | SIZE: 632 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Providers;
     4	
     5	use App\Models\MemberCard;
     6	use App\Models\MemberDocument;
     7	use App\Observers\MemberCardObserver;
     8	use App\Observers\MemberDocumentObserver;
     9	use Illuminate\Support\ServiceProvider;
    10	
    11	class Phase5ServiceProvider extends ServiceProvider
    12	{
    13	    public function register(): void
    14	    {
    15	        $this->app->singleton(\App\Services\Cards\CardService::class);
    16	        $this->app->singleton(\App\Services\Documents\DocumentService::class);
    17	    }
    18	
    19	    public function boot(): void
    20	    {
    21	        MemberCard::observe(MemberCardObserver::class);
    22	        MemberDocument::observe(MemberDocumentObserver::class);
    23	    }
    24	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [342]: app/Providers/Phase5ServiceProvider.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [343/494]: app/Providers/SubscriptionServiceProvider.php
│ LANGUAGE: php | LINES: 38 | SIZE: 1234 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Providers;
     4	
     5	use App\Models\Subscription;
     6	use App\Observers\SubscriptionObserver;
     7	use App\Services\Subscription\RenewalService;
     8	use App\Services\Subscription\SubscriptionCalculatorService;
     9	use App\Services\Subscription\SubscriptionService;
    10	use Illuminate\Console\Scheduling\Schedule;
    11	use Illuminate\Support\ServiceProvider;
    12	
    13	class SubscriptionServiceProvider extends ServiceProvider
    14	{
    15	    public function register(): void
    16	    {
    17	        $this->app->singleton(SubscriptionCalculatorService::class);
    18	        $this->app->singleton(SubscriptionService::class);
    19	        $this->app->singleton(RenewalService::class);
    20	    }
    21	
    22	    public function boot(): void
    23	    {
    24	        Subscription::observe(SubscriptionObserver::class);
    25	
    26	        // Schedule subscription-related tasks
    27	        $this->callAfterResolving(Schedule::class, function (Schedule $schedule) {
    28	            $schedule->command('subscriptions:mark-overdue')
    29	                ->dailyAt('02:00')
    30	                ->withoutOverlapping()
    31	                ->runInBackground();
    32	
    33	            $schedule->command('subscriptions:apply-late-fees')
    34	                ->dailyAt('03:00')
    35	                ->withoutOverlapping()
    36	                ->runInBackground();
    37	        });
    38	    }
    39	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [343]: app/Providers/SubscriptionServiceProvider.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [344/494]: app/Services/Admin/AuditService.php
│ LANGUAGE: php | LINES: 89 | SIZE: 2568 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Services\Admin;
     4	
     5	use Spatie\Activitylog\Models\Activity;
     6	use Illuminate\Support\Collection;
     7	use Illuminate\Database\Eloquent\Builder;
     8	use Carbon\Carbon;
     9	
    10	class AuditService
    11	{
    12	    public function log(
    13	        string $action,
    14	        ?string $description = null,
    15	        ?\Illuminate\Database\Eloquent\Model $subject = null,
    16	        array $properties = [],
    17	        ?string $logName = 'default'
    18	    ): Activity {
    19	        $logger = activity($logName)
    20	            ->causedBy(auth()->user());
    21	
    22	        if ($subject) {
    23	            $logger->performedOn($subject);
    24	        }
    25	
    26	        if (!empty($properties)) {
    27	            $logger->withProperties($properties);
    28	        }
    29	
    30	        return $logger->log($description ?? $action);
    31	    }
    32	
    33	    public function getRecentActivity(int $limit = 50): Collection
    34	    {
    35	        return Activity::with(['causer', 'subject'])
    36	            ->latest()
    37	            ->limit($limit)
    38	            ->get();
    39	    }
    40	
    41	    public function getActivityForSubject(\Illuminate\Database\Eloquent\Model $subject): Collection
    42	    {
    43	        return Activity::where('subject_type', get_class($subject))
    44	            ->where('subject_id', $subject->getKey())
    45	            ->with('causer')
    46	            ->latest()
    47	            ->get();
    48	    }
    49	
    50	    public function getActivityByCauser(\Illuminate\Database\Eloquent\Model $causer): Collection
    51	    {
    52	        return Activity::where('causer_type', get_class($causer))
    53	            ->where('causer_id', $causer->getKey())
    54	            ->with('subject')
    55	            ->latest()
    56	            ->get();
    57	    }
    58	
    59	    public function query(): Builder
    60	    {
    61	        return Activity::query()->with(['causer', 'subject']);
    62	    }
    63	
    64	    public function purgeOlderThan(int $days): int
    65	    {
    66	        $cutoff = Carbon::now()->subDays($days);
    67	
    68	        return Activity::where('created_at', '<', $cutoff)->delete();
    69	    }
    70	
    71	    public function getStatsByAction(Carbon $from, Carbon $to): Collection
    72	    {
    73	        return Activity::whereBetween('created_at', [$from, $to])
    74	            ->selectRaw('description as action, COUNT(*) as count')
    75	            ->groupBy('description')
    76	            ->orderByDesc('count')
    77	            ->get();
    78	    }
    79	
    80	    public function getStatsByUser(Carbon $from, Carbon $to): Collection
    81	    {
    82	        return Activity::whereBetween('created_at', [$from, $to])
    83	            ->whereNotNull('causer_id')
    84	            ->selectRaw('causer_id, causer_type, COUNT(*) as count')
    85	            ->groupBy('causer_id', 'causer_type')
    86	            ->orderByDesc('count')
    87	            ->with('causer')
    88	            ->get();
    89	    }
    90	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [344]: app/Services/Admin/AuditService.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [345/494]: app/Services/Admin/BackupService.php
│ LANGUAGE: php | LINES: 174 | SIZE: 5344 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Services\Admin;
     4	
     5	use Illuminate\Support\Facades\Artisan;
     6	use Illuminate\Support\Facades\Storage;
     7	use Illuminate\Support\Collection;
     8	use Illuminate\Support\Str;
     9	
    10	class BackupService
    11	{
    12	    protected AuditService $auditService;
    13	
    14	    public function __construct(AuditService $auditService)
    15	    {
    16	        $this->auditService = $auditService;
    17	    }
    18	
    19	    public function createBackup(string $type = 'full'): array
    20	    {
    21	        $timestamp = now()->format('Y-m-d_H-i-s');
    22	        $filename = "backup_{$type}_{$timestamp}.sql";
    23	        $path = "backups/{$filename}";
    24	
    25	        try {
    26	            $dbHost = config('database.connections.mysql.host');
    27	            $dbPort = config('database.connections.mysql.port');
    28	            $dbName = config('database.connections.mysql.database');
    29	            $dbUser = config('database.connections.mysql.username');
    30	            $dbPass = config('database.connections.mysql.password');
    31	
    32	            $command = sprintf(
    33	                'mysqldump -h%s -P%s -u%s -p%s %s > %s',
    34	                escapeshellarg($dbHost),
    35	                escapeshellarg($dbPort),
    36	                escapeshellarg($dbUser),
    37	                escapeshellarg($dbPass),
    38	                escapeshellarg($dbName),
    39	                storage_path("app/{$path}")
    40	            );
    41	
    42	            $storageDir = storage_path('app/backups');
    43	            if (!is_dir($storageDir)) {
    44	                mkdir($storageDir, 0755, true);
    45	            }
    46	
    47	            exec($command . ' 2>&1', $output, $returnCode);
    48	
    49	            if ($returnCode !== 0) {
    50	                throw new \RuntimeException('فشل إنشاء النسخة الاحتياطية: ' . implode("\n", $output));
    51	            }
    52	
    53	            $size = Storage::size($path);
    54	
    55	            $this->auditService->log(
    56	                'backup_created',
    57	                "تم إنشاء نسخة احتياطية: {$filename}",
    58	                null,
    59	                ['filename' => $filename, 'size' => $size, 'type' => $type]
    60	            );
    61	
    62	            return [
    63	                'success' => true,
    64	                'filename' => $filename,
    65	                'path' => $path,
    66	                'size' => $size,
    67	                'created_at' => now()->toDateTimeString(),
    68	            ];
    69	        } catch (\Throwable $e) {
    70	            $this->auditService->log(
    71	                'backup_failed',
    72	                "فشل إنشاء النسخة الاحتياطية: {$e->getMessage()}",
    73	                null,
    74	                ['error' => $e->getMessage()]
    75	            );
    76	
    77	            return [
    78	                'success' => false,
    79	                'error' => $e->getMessage(),
    80	            ];
    81	        }
    82	    }
    83	
    84	    public function listBackups(): Collection
    85	    {
    86	        $files = Storage::files('backups');
    87	
    88	        return collect($files)
    89	            ->filter(fn ($f) => Str::endsWith($f, '.sql'))
    90	            ->map(function ($file) {
    91	                return [
    92	                    'filename' => basename($file),
    93	                    'path' => $file,
    94	                    'size' => Storage::size($file),
    95	                    'size_formatted' => $this->formatBytes(Storage::size($file)),
    96	                    'last_modified' => \Carbon\Carbon::createFromTimestamp(Storage::lastModified($file)),
    97	                ];
    98	            })
    99	            ->sortByDesc('last_modified')
   100	            ->values();
   101	    }
   102	
   103	    public function deleteBackup(string $filename): bool
   104	    {
   105	        $path = "backups/{$filename}";
   106	
   107	        if (!Storage::exists($path)) {
   108	            throw new \InvalidArgumentException("النسخة الاحتياطية غير موجودة: {$filename}");
   109	        }
   110	
   111	        $deleted = Storage::delete($path);
   112	
   113	        if ($deleted) {
   114	            $this->auditService->log(
   115	                'backup_deleted',
   116	                "تم حذف النسخة الاحتياطية: {$filename}",
   117	                null,
   118	                ['filename' => $filename]
   119	            );
   120	        }
   121	
   122	        return $deleted;
   123	    }
   124	
   125	    public function downloadPath(string $filename): string
   126	    {
   127	        $path = "backups/{$filename}";
   128	
   129	        if (!Storage::exists($path)) {
   130	            throw new \InvalidArgumentException("النسخة الاحتياطية غير موجودة: {$filename}");
   131	        }
   132	
   133	        return Storage::path($path);
   134	    }
   135	
   136	    public function cleanOldBackups(int $keepCount = 10): int
   137	    {
   138	        $backups = $this->listBackups();
   139	
   140	        if ($backups->count() <= $keepCount) {
   141	            return 0;
   142	        }
   143	
   144	        $toDelete = $backups->slice($keepCount);
   145	        $deleted = 0;
   146	
   147	        foreach ($toDelete as $backup) {
   148	            if (Storage::delete($backup['path'])) {
   149	                $deleted++;
   150	            }
   151	        }
   152	
   153	        if ($deleted > 0) {
   154	            $this->auditService->log(
   155	                'backup_cleanup',
   156	                "تم تنظيف {$deleted} نسخة احتياطية قديمة",
   157	                null,
   158	                ['deleted_count' => $deleted]
   159	            );
   160	        }
   161	
   162	        return $deleted;
   163	    }
   164	
   165	    protected function formatBytes(int $bytes, int $precision = 2): string
   166	    {
   167	        $units = ['B', 'KB', 'MB', 'GB', 'TB'];
   168	        $bytes = max($bytes, 0);
   169	        $pow = floor(($bytes ? log($bytes) : 0) / log(1024));
   170	        $pow = min($pow, count($units) - 1);
   171	        $bytes /= pow(1024, $pow);
   172	
   173	        return round($bytes, $precision) . ' ' . $units[$pow];
   174	    }
   175	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [345]: app/Services/Admin/BackupService.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [346/494]: app/Services/Admin/BoardService.php
│ LANGUAGE: php | LINES: 195 | SIZE: 7302 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Services\Admin;
     4	
     5	use App\Models\BoardOffer;
     6	use App\Models\BoardDecision;
     7	use App\Enums\BoardOfferStatus;
     8	use App\Enums\BoardDecisionType;
     9	use App\Enums\BoardDecisionResult;
    10	use Illuminate\Support\Facades\DB;
    11	
    12	class BoardService
    13	{
    14	    protected SequenceService $sequenceService;
    15	    protected AuditService $auditService;
    16	
    17	    public function __construct(SequenceService $sequenceService, AuditService $auditService)
    18	    {
    19	        $this->sequenceService = $sequenceService;
    20	        $this->auditService = $auditService;
    21	    }
    22	
    23	    public function createOffer(array $data): BoardOffer
    24	    {
    25	        return DB::transaction(function () use ($data) {
    26	            $data['offer_number'] = $this->sequenceService->getNextNumber('board_offer_number');
    27	            $data['status'] = BoardOfferStatus::PENDING;
    28	            $data['submitted_by'] = auth()->id();
    29	            $data['submitted_at'] = now();
    30	
    31	            $offer = BoardOffer::create($data);
    32	
    33	            $this->auditService->log(
    34	                'board_offer_created',
    35	                "تم إنشاء عرض مجلس جديد: {$offer->offer_number}",
    36	                $offer,
    37	                ['offer_number' => $offer->offer_number]
    38	            );
    39	
    40	            return $offer;
    41	        });
    42	    }
    43	
    44	    public function submitForReview(BoardOffer $offer): BoardOffer
    45	    {
    46	        $this->validateTransition($offer, BoardOfferStatus::UNDER_REVIEW);
    47	
    48	        return DB::transaction(function () use ($offer) {
    49	            $offer->update([
    50	                'status' => BoardOfferStatus::UNDER_REVIEW,
    51	            ]);
    52	
    53	            $this->auditService->log(
    54	                'board_offer_submitted',
    55	                "تم تقديم العرض للمراجعة: {$offer->offer_number}",
    56	                $offer
    57	            );
    58	
    59	            return $offer->fresh();
    60	        });
    61	    }
    62	
    63	    public function recordDecision(BoardOffer $offer, array $decisionData): BoardDecision
    64	    {
    65	        return DB::transaction(function () use ($offer, $decisionData) {
    66	            $decision = $offer->decisions()->create([
    67	                'decision_type' => $decisionData['decision_type'],
    68	                'result' => $decisionData['result'],
    69	                'decision_date' => $decisionData['decision_date'] ?? now(),
    70	                'notes' => $decisionData['notes'] ?? null,
    71	                'decided_by' => auth()->id(),
    72	                'voting_for' => $decisionData['voting_for'] ?? null,
    73	                'voting_against' => $decisionData['voting_against'] ?? null,
    74	                'voting_abstain' => $decisionData['voting_abstain'] ?? null,
    75	                'conditions' => $decisionData['conditions'] ?? null,
    76	                'effective_date' => $decisionData['effective_date'] ?? null,
    77	                'expiry_date' => $decisionData['expiry_date'] ?? null,
    78	            ]);
    79	
    80	            $newStatus = match ($decisionData['result']) {
    81	                BoardDecisionResult::APPROVED => BoardOfferStatus::APPROVED,
    82	                BoardDecisionResult::REJECTED => BoardOfferStatus::REJECTED,
    83	                BoardDecisionResult::DEFERRED => BoardOfferStatus::DEFERRED,
    84	                BoardDecisionResult::RETURNED => BoardOfferStatus::RETURNED,
    85	                default => $offer->status,
    86	            };
    87	
    88	            $offer->update([
    89	                'status' => $newStatus,
    90	                'reviewed_by' => auth()->id(),
    91	                'reviewed_at' => now(),
    92	            ]);
    93	
    94	            $this->auditService->log(
    95	                'board_decision_recorded',
    96	                "تم تسجيل قرار المجلس للعرض: {$offer->offer_number} - النتيجة: {$decisionData['result']->getLabel()}",
    97	                $decision,
    98	                [
    99	                    'offer_number' => $offer->offer_number,
   100	                    'result' => $decisionData['result']->value,
   101	                ]
   102	            );
   103	
   104	            return $decision;
   105	        });
   106	    }
   107	
   108	    public function cancelOffer(BoardOffer $offer, ?string $reason = null): BoardOffer
   109	    {
   110	        $this->validateTransition($offer, BoardOfferStatus::CANCELLED);
   111	
   112	        return DB::transaction(function () use ($offer, $reason) {
   113	            $offer->update([
   114	                'status' => BoardOfferStatus::CANCELLED,
   115	                'cancellation_reason' => $reason,
   116	                'cancelled_at' => now(),
   117	                'cancelled_by' => auth()->id(),
   118	            ]);
   119	
   120	            $this->auditService->log(
   121	                'board_offer_cancelled',
   122	                "تم إلغاء العرض: {$offer->offer_number}",
   123	                $offer,
   124	                ['reason' => $reason]
   125	            );
   126	
   127	            return $offer->fresh();
   128	        });
   129	    }
   130	
   131	    public function reopenOffer(BoardOffer $offer): BoardOffer
   132	    {
   133	        if (!in_array($offer->status, [BoardOfferStatus::DEFERRED, BoardOfferStatus::RETURNED])) {
   134	            throw new \LogicException("لا يمكن إعادة فتح عرض بحالة: {$offer->status->getLabel()}");
   135	        }
   136	
   137	        return DB::transaction(function () use ($offer) {
   138	            $offer->update([
   139	                'status' => BoardOfferStatus::UNDER_REVIEW,
   140	            ]);
   141	
   142	            $this->auditService->log(
   143	                'board_offer_reopened',
   144	                "تم إعادة فتح العرض: {$offer->offer_number}",
   145	                $offer
   146	            );
   147	
   148	            return $offer->fresh();
   149	        });
   150	    }
   151	
   152	    public function getPendingOffers(): \Illuminate\Database\Eloquent\Collection
   153	    {
   154	        return BoardOffer::whereIn('status', [
   155	            BoardOfferStatus::PENDING,
   156	            BoardOfferStatus::UNDER_REVIEW,
   157	        ])
   158	            ->with(['member', 'submittedBy'])
   159	            ->orderBy('submitted_at')
   160	            ->get();
   161	    }
   162	
   163	    public function getOfferStats(): array
   164	    {
   165	        $total = BoardOffer::count();
   166	        $pending = BoardOffer::where('status', BoardOfferStatus::PENDING)->count();
   167	        $underReview = BoardOffer::where('status', BoardOfferStatus::UNDER_REVIEW)->count();
   168	        $approved = BoardOffer::where('status', BoardOfferStatus::APPROVED)->count();
   169	        $rejected = BoardOffer::where('status', BoardOfferStatus::REJECTED)->count();
   170	
   171	        return [
   172	            'total' => $total,
   173	            'pending' => $pending,
   174	            'under_review' => $underReview,
   175	            'approved' => $approved,
   176	            'rejected' => $rejected,
   177	        ];
   178	    }
   179	
   180	    protected function validateTransition(BoardOffer $offer, BoardOfferStatus $newStatus): void
   181	    {
   182	        $allowed = match ($offer->status) {
   183	            BoardOfferStatus::PENDING => [BoardOfferStatus::UNDER_REVIEW, BoardOfferStatus::CANCELLED],
   184	            BoardOfferStatus::UNDER_REVIEW => [BoardOfferStatus::APPROVED, BoardOfferStatus::REJECTED, BoardOfferStatus::DEFERRED, BoardOfferStatus::RETURNED, BoardOfferStatus::CANCELLED],
   185	            BoardOfferStatus::DEFERRED => [BoardOfferStatus::UNDER_REVIEW, BoardOfferStatus::CANCELLED],
   186	            BoardOfferStatus::RETURNED => [BoardOfferStatus::UNDER_REVIEW, BoardOfferStatus::CANCELLED],
   187	            default => [],
   188	        };
   189	
   190	        if (!in_array($newStatus, $allowed)) {
   191	            throw new \LogicException(
   192	                "لا يمكن الانتقال من حالة '{$offer->status->getLabel()}' إلى '{$newStatus->getLabel()}'"
   193	            );
   194	        }
   195	    }
   196	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [346]: app/Services/Admin/BoardService.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [347/494]: app/Services/Admin/CashRegisterService.php
│ LANGUAGE: php | LINES: 17 | SIZE: 500 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	/**
     4	 * @deprecated Use App\Services\Financial\CashRegisterService instead.
     5	 * This file exists only for backward compatibility. Remove after audit.
     6	 */
     7	
     8	declare(strict_types=1);
     9	
    10	namespace App\Services\Admin;
    11	
    12	use App\Services\Financial\CashRegisterService as FinancialCashRegisterService;
    13	
    14	class CashRegisterService extends FinancialCashRegisterService
    15	{
    16	    // Inherits everything from the Financial version.
    17	    // All callers should migrate to App\Services\Financial\CashRegisterService.
    18	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [347]: app/Services/Admin/CashRegisterService.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [348/494]: app/Services/Admin/NotificationDispatchService.php
│ LANGUAGE: php | LINES: 188 | SIZE: 6210 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Services\Admin;
     4	
     5	use App\Models\Notification;
     6	use App\Models\Member;
     7	use App\Models\User;
     8	use App\Enums\NotificationChannel;
     9	use App\Enums\NotificationPriority;
    10	use Illuminate\Support\Facades\DB;
    11	use Illuminate\Support\Collection;
    12	
    13	class NotificationDispatchService
    14	{
    15	    protected SettingsService $settingsService;
    16	
    17	    public function __construct(SettingsService $settingsService)
    18	    {
    19	        $this->settingsService = $settingsService;
    20	    }
    21	
    22	    public function send(
    23	        \Illuminate\Database\Eloquent\Model $notifiable,
    24	        string $titleAr,
    25	        string $bodyAr,
    26	        NotificationChannel $channel = NotificationChannel::SYSTEM,
    27	        NotificationPriority $priority = NotificationPriority::NORMAL,
    28	        ?string $titleEn = null,
    29	        ?string $bodyEn = null,
    30	        ?string $actionUrl = null,
    31	        ?string $category = null,
    32	        array $extraData = [],
    33	    ): Notification {
    34	        return DB::transaction(function () use (
    35	            $notifiable, $titleAr, $bodyAr, $channel, $priority,
    36	            $titleEn, $bodyEn, $actionUrl, $category, $extraData
    37	        ) {
    38	            $notification = Notification::create([
    39	                'notifiable_type' => get_class($notifiable),
    40	                'notifiable_id' => $notifiable->getKey(),
    41	                'channel' => $channel,
    42	                'priority' => $priority,
    43	                'title_ar' => $titleAr,
    44	                'title_en' => $titleEn,
    45	                'body_ar' => $bodyAr,
    46	                'body_en' => $bodyEn,
    47	                'action_url' => $actionUrl,
    48	                'category' => $category,
    49	                'data' => $extraData,
    50	                'sent_at' => now(),
    51	            ]);
    52	
    53	            $this->dispatchToChannel($notification, $channel);
    54	
    55	            return $notification;
    56	        });
    57	    }
    58	
    59	    public function sendBulk(
    60	        Collection $notifiables,
    61	        string $titleAr,
    62	        string $bodyAr,
    63	        NotificationChannel $channel = NotificationChannel::SYSTEM,
    64	        NotificationPriority $priority = NotificationPriority::NORMAL,
    65	        ?string $titleEn = null,
    66	        ?string $bodyEn = null,
    67	        ?string $actionUrl = null,
    68	        ?string $category = null,
    69	        array $extraData = [],
    70	    ): int {
    71	        $count = 0;
    72	
    73	        DB::transaction(function () use (
    74	            $notifiables, $titleAr, $bodyAr, $channel, $priority,
    75	            $titleEn, $bodyEn, $actionUrl, $category, $extraData, &$count
    76	        ) {
    77	            $records = [];
    78	
    79	            foreach ($notifiables as $notifiable) {
    80	                $records[] = [
    81	                    'notifiable_type' => get_class($notifiable),
    82	                    'notifiable_id' => $notifiable->getKey(),
    83	                    'channel' => $channel->value,
    84	                    'priority' => $priority->value,
    85	                    'title_ar' => $titleAr,
    86	                    'title_en' => $titleEn,
    87	                    'body_ar' => $bodyAr,
    88	                    'body_en' => $bodyEn,
    89	                    'action_url' => $actionUrl,
    90	                    'category' => $category,
    91	                    'data' => json_encode($extraData, JSON_UNESCAPED_UNICODE),
    92	                    'sent_at' => now(),
    93	                    'created_at' => now(),
    94	                    'updated_at' => now(),
    95	                ];
    96	            }
    97	
    98	            foreach (array_chunk($records, 500) as $chunk) {
    99	                Notification::insert($chunk);
   100	                $count += count($chunk);
   101	            }
   102	        });
   103	
   104	        return $count;
   105	    }
   106	
   107	    public function markAsRead(Notification $notification): Notification
   108	    {
   109	        $notification->update(['read_at' => now()]);
   110	        return $notification->fresh();
   111	    }
   112	
   113	    public function markAllAsRead(\Illuminate\Database\Eloquent\Model $notifiable): int
   114	    {
   115	        return Notification::where('notifiable_type', get_class($notifiable))
   116	            ->where('notifiable_id', $notifiable->getKey())
   117	            ->whereNull('read_at')
   118	            ->update(['read_at' => now()]);
   119	    }
   120	
   121	    public function getUnreadCount(\Illuminate\Database\Eloquent\Model $notifiable): int
   122	    {
   123	        return Notification::where('notifiable_type', get_class($notifiable))
   124	            ->where('notifiable_id', $notifiable->getKey())
   125	            ->whereNull('read_at')
   126	            ->count();
   127	    }
   128	
   129	    public function getForNotifiable(
   130	        \Illuminate\Database\Eloquent\Model $notifiable,
   131	        ?NotificationChannel $channel = null,
   132	        ?string $category = null,
   133	        bool $unreadOnly = false,
   134	        int $limit = 50
   135	    ): Collection {
   136	        $query = Notification::where('notifiable_type', get_class($notifiable))
   137	            ->where('notifiable_id', $notifiable->getKey());
   138	
   139	        if ($channel) {
   140	            $query->where('channel', $channel);
   141	        }
   142	
   143	        if ($category) {
   144	            $query->where('category', $category);
   145	        }
   146	
   147	        if ($unreadOnly) {
   148	            $query->whereNull('read_at');
   149	        }
   150	
   151	        return $query->latest('sent_at')->limit($limit)->get();
   152	    }
   153	
   154	    public function deleteOlderThan(int $days): int
   155	    {
   156	        return Notification::where('created_at', '<', now()->subDays($days))
   157	            ->whereNotNull('read_at')
   158	            ->delete();
   159	    }
   160	
   161	    protected function dispatchToChannel(Notification $notification, NotificationChannel $channel): void
   162	    {
   163	        match ($channel) {
   164	            NotificationChannel::SYSTEM => null, // Already stored in DB
   165	            NotificationChannel::EMAIL => $this->sendEmail($notification),
   166	            NotificationChannel::SMS => $this->sendSms($notification),
   167	            default => null,
   168	        };
   169	    }
   170	
   171	    protected function sendEmail(Notification $notification): void
   172	    {
   173	        if (!$this->settingsService->get('notification.email_enabled', false)) {
   174	            return;
   175	        }
   176	
   177	        // Email dispatch would be queued here
   178	        // Mail::to($notification->notifiable)->queue(new NotificationMail($notification));
   179	    }
   180	
   181	    protected function sendSms(Notification $notification): void
   182	    {
   183	        if (!$this->settingsService->get('notification.sms_enabled', false)) {
   184	            return;
   185	        }
   186	
   187	        // SMS dispatch would be handled by provider integration
   188	    }
   189	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [348]: app/Services/Admin/NotificationDispatchService.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [349/494]: app/Services/Admin/SequenceService.php
│ LANGUAGE: php | LINES: 17 | SIZE: 476 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	/**
     4	 * @deprecated Use App\Services\Financial\SequenceService instead.
     5	 * This file exists only for backward compatibility. Remove after audit.
     6	 */
     7	
     8	declare(strict_types=1);
     9	
    10	namespace App\Services\Admin;
    11	
    12	use App\Services\Financial\SequenceService as FinancialSequenceService;
    13	
    14	class SequenceService extends FinancialSequenceService
    15	{
    16	    // Inherits everything from the Financial version.
    17	    // All callers should migrate to App\Services\Financial\SequenceService.
    18	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [349]: app/Services/Admin/SequenceService.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [350/494]: app/Services/Admin/SettingsService.php
│ LANGUAGE: php | LINES: 102 | SIZE: 2769 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Services\Admin;
     4	
     5	use App\Models\SystemSetting;
     6	use Illuminate\Support\Facades\Cache;
     7	use Illuminate\Support\Collection;
     8	
     9	class SettingsService
    10	{
    11	    protected const CACHE_KEY = 'system_settings';
    12	    protected const CACHE_TTL = 3600; // 1 hour
    13	
    14	    public function get(string $key, mixed $default = null): mixed
    15	    {
    16	        $settings = $this->getAllCached();
    17	
    18	        $setting = $settings->firstWhere('setting_key', $key);
    19	
    20	        if (!$setting) {
    21	            return $default;
    22	        }
    23	
    24	        return $this->castValue($setting->setting_value, $setting->value_type);
    25	    }
    26	
    27	    public function set(string $key, mixed $value): SystemSetting
    28	    {
    29	        $setting = SystemSetting::where('setting_key', $key)->first();
    30	
    31	        if (!$setting) {
    32	            throw new \InvalidArgumentException("Setting key '{$key}' does not exist.");
    33	        }
    34	
    35	        $setting->update([
    36	            'setting_value' => $this->serializeValue($value, $setting->value_type),
    37	        ]);
    38	
    39	        $this->clearCache();
    40	
    41	        return $setting->fresh();
    42	    }
    43	
    44	    public function setMany(array $settings): void
    45	    {
    46	        foreach ($settings as $key => $value) {
    47	            $this->set($key, $value);
    48	        }
    49	    }
    50	
    51	    public function getGroup(string $groupKey): Collection
    52	    {
    53	        return $this->getAllCached()
    54	            ->where('group_key', $groupKey)
    55	            ->sortBy('display_order');
    56	    }
    57	
    58	    public function getGrouped(): Collection
    59	    {
    60	        return $this->getAllCached()
    61	            ->sortBy('display_order')
    62	            ->groupBy('group_key');
    63	    }
    64	
    65	    public function getSubGrouped(): Collection
    66	    {
    67	        return $this->getAllCached()
    68	            ->sortBy('display_order')
    69	            ->groupBy(fn ($s) => $s->group_key . '.' . $s->sub_group);
    70	    }
    71	
    72	    public function getAllCached(): Collection
    73	    {
    74	        return Cache::remember(self::CACHE_KEY, self::CACHE_TTL, function () {
    75	            return SystemSetting::orderBy('display_order')->get();
    76	        });
    77	    }
    78	
    79	    public function clearCache(): void
    80	    {
    81	        Cache::forget(self::CACHE_KEY);
    82	    }
    83	
    84	    protected function castValue(mixed $value, string $type): mixed
    85	    {
    86	        return match ($type) {
    87	            'boolean' => filter_var($value, FILTER_VALIDATE_BOOLEAN),
    88	            'integer' => (int) $value,
    89	            'float', 'decimal' => (float) $value,
    90	            'json', 'array' => json_decode($value, true),
    91	            default => $value,
    92	        };
    93	    }
    94	
    95	    protected function serializeValue(mixed $value, string $type): string
    96	    {
    97	        return match ($type) {
    98	            'boolean' => $value ? 'true' : 'false',
    99	            'json', 'array' => json_encode($value, JSON_UNESCAPED_UNICODE),
   100	            default => (string) $value,
   101	        };
   102	    }
   103	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [350]: app/Services/Admin/SettingsService.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [351/494]: app/Services/Cards/CardService.php
│ LANGUAGE: php | LINES: 449 | SIZE: 15437 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Services\Cards;
     4	
     5	use App\Enums\CardStatus;
     6	use App\Enums\AuditAction;
     7	use App\Models\Member;
     8	use App\Models\MemberCard;
     9	use App\Models\SequenceCounter;
    10	use Illuminate\Support\Facades\Auth;
    11	use Illuminate\Support\Facades\DB;
    12	use Illuminate\Support\Str;
    13	use SimpleSoftwareIO\QrCode\Facades\QrCode;
    14	
    15	class CardService
    16	{
    17	    /**
    18	     * Issue a new card for a member.
    19	     */
    20	    public function issueCard(Member $member, string $cardType = 'main', ?string $notes = null, ?int $previousCardId = null, ?string $replacementReason = null): MemberCard
    21	    {
    22	        return DB::transaction(function () use ($member, $cardType, $notes, $previousCardId, $replacementReason) {
    23	            // Cancel any existing active/printed cards if issuing main or renewal
    24	            if (in_array($cardType, ['main', 'renewal'])) {
    25	                MemberCard::where('member_id', $member->id)
    26	                    ->whereIn('status', [CardStatus::ACTIVE->value, CardStatus::PRINTED->value])
    27	                    ->update([
    28	                        'status' => CardStatus::EXPIRED->value,
    29	                        'updated_by' => Auth::id(),
    30	                    ]);
    31	            }
    32	
    33	            // If replacement, cancel the previous card
    34	            if ($cardType === 'replacement' && $previousCardId) {
    35	                MemberCard::where('id', $previousCardId)
    36	                    ->update([
    37	                        'status' => CardStatus::REPLACED->value,
    38	                        'updated_by' => Auth::id(),
    39	                    ]);
    40	            }
    41	
    42	            $cardNumber = $this->generateCardNumber();
    43	            $barcodeData = $this->generateBarcodeData($member, $cardNumber);
    44	            $qrCodeData = $this->generateQrCodeData($member, $cardNumber);
    45	            $fiscalYear = $this->getCurrentFiscalYear();
    46	            $expiryDate = $this->calculateExpiryDate($member);
    47	
    48	            $card = MemberCard::create([
    49	                'member_id' => $member->id,
    50	                'card_number' => $cardNumber,
    51	                'card_type' => $cardType,
    52	                'status' => CardStatus::ACTIVE->value,
    53	                'issue_date' => now()->toDateString(),
    54	                'expiry_date' => $expiryDate,
    55	                'photo_path' => $member->photo_path,
    56	                'barcode_data' => $barcodeData,
    57	                'qr_code_data' => $qrCodeData,
    58	                'replacement_reason' => $replacementReason,
    59	                'previous_card_id' => $previousCardId,
    60	                'printing_cost' => $this->getPrintingCost($cardType),
    61	                'notes' => $notes,
    62	                'created_by' => Auth::id(),
    63	                'updated_by' => Auth::id(),
    64	            ]);
    65	
    66	            activity()
    67	                ->performedOn($card)
    68	                ->causedBy(Auth::user())
    69	                ->withProperties([
    70	                    'card_number' => $cardNumber,
    71	                    'card_type' => $cardType,
    72	                    'member_id' => $member->id,
    73	                    'membership_number' => $member->membership_number,
    74	                ])
    75	                ->log(AuditAction::CREATE->value);
    76	
    77	            return $card;
    78	        });
    79	    }
    80	
    81	    /**
    82	     * Mark a card as printed.
    83	     */
    84	    public function markAsPrinted(MemberCard $card): MemberCard
    85	    {
    86	        $card->update([
    87	            'status' => CardStatus::PRINTED->value,
    88	            'printed_at' => now(),
    89	            'printed_by' => Auth::id(),
    90	            'updated_by' => Auth::id(),
    91	        ]);
    92	
    93	        activity()
    94	            ->performedOn($card)
    95	            ->causedBy(Auth::user())
    96	            ->withProperties([
    97	                'card_number' => $card->card_number,
    98	                'action' => 'printed',
    99	            ])
   100	            ->log('card_printed');
   101	
   102	        return $card->fresh();
   103	    }
   104	
   105	    /**
   106	     * Mark a card as collected by the member.
   107	     */
   108	    public function markAsCollected(MemberCard $card): MemberCard
   109	    {
   110	        $card->update([
   111	            'status' => CardStatus::COLLECTED->value,
   112	            'collected_at' => now(),
   113	            'collected_by' => Auth::id(),
   114	            'updated_by' => Auth::id(),
   115	        ]);
   116	
   117	        activity()
   118	            ->performedOn($card)
   119	            ->causedBy(Auth::user())
   120	            ->withProperties([
   121	                'card_number' => $card->card_number,
   122	                'action' => 'collected',
   123	            ])
   124	            ->log('card_collected');
   125	
   126	        return $card->fresh();
   127	    }
   128	
   129	    /**
   130	     * Suspend a card.
   131	     */
   132	    public function suspendCard(MemberCard $card, ?string $reason = null): MemberCard
   133	    {
   134	        $card->update([
   135	            'status' => CardStatus::SUSPENDED->value,
   136	            'notes' => $reason ? ($card->notes ? $card->notes . "\n" . $reason : $reason) : $card->notes,
   137	            'updated_by' => Auth::id(),
   138	        ]);
   139	
   140	        activity()
   141	            ->performedOn($card)
   142	            ->causedBy(Auth::user())
   143	            ->withProperties([
   144	                'card_number' => $card->card_number,
   145	                'reason' => $reason,
   146	                'action' => 'suspended',
   147	            ])
   148	            ->log('card_suspended');
   149	
   150	        return $card->fresh();
   151	    }
   152	
   153	    /**
   154	     * Cancel a card.
   155	     */
   156	    public function cancelCard(MemberCard $card, ?string $reason = null): MemberCard
   157	    {
   158	        $card->update([
   159	            'status' => CardStatus::CANCELLED->value,
   160	            'notes' => $reason ? ($card->notes ? $card->notes . "\n" . $reason : $reason) : $card->notes,
   161	            'updated_by' => Auth::id(),
   162	        ]);
   163	
   164	        activity()
   165	            ->performedOn($card)
   166	            ->causedBy(Auth::user())
   167	            ->withProperties([
   168	                'card_number' => $card->card_number,
   169	                'reason' => $reason,
   170	                'action' => 'cancelled',
   171	            ])
   172	            ->log('card_cancelled');
   173	
   174	        return $card->fresh();
   175	    }
   176	
   177	    /**
   178	     * Reactivate a suspended card.
   179	     */
   180	    public function reactivateCard(MemberCard $card): MemberCard
   181	    {
   182	        $card->update([
   183	            'status' => CardStatus::ACTIVE->value,
   184	            'updated_by' => Auth::id(),
   185	        ]);
   186	
   187	        activity()
   188	            ->performedOn($card)
   189	            ->causedBy(Auth::user())
   190	            ->withProperties([
   191	                'card_number' => $card->card_number,
   192	                'action' => 'reactivated',
   193	            ])
   194	            ->log('card_reactivated');
   195	
   196	        return $card->fresh();
   197	    }
   198	
   199	    /**
   200	     * Check if a member is eligible for card printing.
   201	     */
   202	    public function canPrintCard(Member $member): array
   203	    {
   204	        $errors = [];
   205	
   206	        // Check membership status is active
   207	        if ($member->status && $member->status->status_key !== 'active') {
   208	            $errors[] = 'حالة العضوية غير نشطة. الحالة الحالية: ' . ($member->status->name_ar ?? 'غير معروف');
   209	        }
   210	
   211	        // Check subscription is current
   212	        $currentYear = (int) date('Y');
   213	        if ($member->paid_through_year < $currentYear) {
   214	            $errors[] = 'الاشتراك غير مسدد للعام الحالي (' . $currentYear . '). مسدد حتى: ' . $member->paid_through_year;
   215	        }
   216	
   217	        // Check photo uploaded
   218	        if (empty($member->photo_path)) {
   219	            $errors[] = 'لم يتم رفع صورة شخصية للعضو.';
   220	        }
   221	
   222	        // Check if there's an active card that hasn't expired
   223	        $activeCard = MemberCard::where('member_id', $member->id)
   224	            ->whereIn('status', [CardStatus::ACTIVE->value, CardStatus::PRINTED->value])
   225	            ->where('expiry_date', '>=', now()->toDateString())
   226	            ->first();
   227	
   228	        if ($activeCard) {
   229	            $errors[] = 'يوجد كارنيه ساري الصلاحية بالفعل رقم: ' . $activeCard->card_number;
   230	        }
   231	
   232	        return [
   233	            'eligible' => empty($errors),
   234	            'errors' => $errors,
   235	        ];
   236	    }
   237	
   238	    /**
   239	     * Get card data formatted for preview/print.
   240	     */
   241	    public function getCardPrintData(MemberCard $card): array
   242	    {
   243	        $member = $card->member()->with(['membershipType', 'status'])->first();
   244	
   245	        return [
   246	            'card' => $card,
   247	            'member' => $member,
   248	            'front' => [
   249	                'photo_url' => $member->photo_path ? asset('storage/' . $member->photo_path) : asset('images/default-avatar.png'),
   250	                'full_name_ar' => $member->full_name_ar,
   251	                'full_name_en' => $member->full_name_en,
   252	                'membership_number' => $member->membership_number,
   253	                'membership_type_ar' => $member->membershipType->name_ar ?? '',
   254	                'membership_type_en' => $member->membershipType->name_en ?? '',
   255	                'status_ar' => $member->status->name_ar ?? '',
   256	                'status_color' => $this->getStatusColor($member->status->status_key ?? ''),
   257	                'barcode_data' => $card->barcode_data,
   258	                'qr_code_svg' => $this->renderQrCode($card->qr_code_data),
   259	                'since_year' => $member->activation_date ? date('Y', strtotime($member->activation_date)) : '',
   260	                'issue_date' => $card->issue_date,
   261	                'expiry_date' => $card->expiry_date,
   262	            ],
   263	            'back' => [
   264	                'national_id' => $member->national_id,
   265	                'blood_type' => $member->blood_type ?? 'غير محدد',
   266	                'emergency_contact_name' => $member->emergency_contact_name ?? '',
   267	                'emergency_contact_phone' => $member->emergency_contact_phone ?? '',
   268	                'expiry_year' => $card->expiry_date ? date('Y', strtotime($card->expiry_date)) : '',
   269	                'card_number' => $card->card_number,
   270	                'club_name_ar' => $this->getClubSetting('name_ar', 'النادي'),
   271	                'club_name_en' => $this->getClubSetting('name_en', 'The Club'),
   272	                'club_address' => $this->getClubSetting('address_ar', ''),
   273	                'club_phone' => $this->getClubSetting('phone_primary', ''),
   274	            ],
   275	        ];
   276	    }
   277	
   278	    /**
   279	     * Bulk print cards.
   280	     */
   281	    public function bulkMarkAsPrinted(array $cardIds): int
   282	    {
   283	        $count = MemberCard::whereIn('id', $cardIds)
   284	            ->where('status', CardStatus::ACTIVE->value)
   285	            ->update([
   286	                'status' => CardStatus::PRINTED->value,
   287	                'printed_at' => now(),
   288	                'printed_by' => Auth::id(),
   289	                'updated_by' => Auth::id(),
   290	            ]);
   291	
   292	        activity()
   293	            ->causedBy(Auth::user())
   294	            ->withProperties([
   295	                'card_ids' => $cardIds,
   296	                'count' => $count,
   297	                'action' => 'bulk_printed',
   298	            ])
   299	            ->log('cards_bulk_printed');
   300	
   301	        return $count;
   302	    }
   303	
   304	    /**
   305	     * Get expired cards that need renewal.
   306	     */
   307	    public function getExpiredCards(): \Illuminate\Database\Eloquent\Collection
   308	    {
   309	        return MemberCard::with('member')
   310	            ->whereIn('status', [CardStatus::ACTIVE->value, CardStatus::PRINTED->value, CardStatus::COLLECTED->value])
   311	            ->where('expiry_date', '<', now()->toDateString())
   312	            ->where('is_archived', false)
   313	            ->get();
   314	    }
   315	
   316	    /**
   317	     * Auto-expire cards past their expiry date.
   318	     */
   319	    public function autoExpireCards(): int
   320	    {
   321	        return MemberCard::whereIn('status', [CardStatus::ACTIVE->value, CardStatus::PRINTED->value, CardStatus::COLLECTED->value])
   322	            ->where('expiry_date', '<', now()->toDateString())
   323	            ->where('is_archived', false)
   324	            ->update([
   325	                'status' => CardStatus::EXPIRED->value,
   326	                'updated_by' => Auth::id(),
   327	            ]);
   328	    }
   329	
   330	    /**
   331	     * Generate unique card number from sequence.
   332	     */
   333	    protected function generateCardNumber(): string
   334	    {
   335	        return DB::transaction(function () {
   336	            $sequence = SequenceCounter::where('sequence_key', 'card_number')->lockForUpdate()->first();
   337	
   338	            if (!$sequence) {
   339	                throw new \RuntimeException('Card number sequence not configured.');
   340	            }
   341	
   342	            $newValue = $sequence->current_value + $sequence->increment_by;
   343	            $sequence->update(['current_value' => $newValue]);
   344	
   345	            return $sequence->prefix . str_pad((string) $newValue, $sequence->pad_length, '0', STR_PAD_LEFT) . $sequence->suffix;
   346	        });
   347	    }
   348	
   349	    /**
   350	     * Generate barcode data string.
   351	     */
   352	    protected function generateBarcodeData(Member $member, string $cardNumber): string
   353	    {
   354	        return $member->membership_number . '-' . Str::after($cardNumber, '-');
   355	    }
   356	
   357	    /**
   358	     * Generate QR code data as JSON.
   359	     */
   360	    protected function generateQrCodeData(Member $member, string $cardNumber): string
   361	    {
   362	        return json_encode([
   363	            'cn' => $cardNumber,
   364	            'mn' => $member->membership_number,
   365	            'name' => $member->full_name_ar,
   366	            'type' => $member->membershipType->type_key ?? '',
   367	            'iss' => now()->toDateString(),
   368	        ], JSON_UNESCAPED_UNICODE);
   369	    }
   370	
   371	    /**
   372	     * Render QR code SVG from data.
   373	     */
   374	    protected function renderQrCode(string $data): string
   375	    {
   376	        try {
   377	            if (class_exists(\SimpleSoftwareIO\QrCode\Facades\QrCode::class)) {
   378	                return QrCode::size(120)->generate($data)->toHtml();
   379	            }
   380	        } catch (\Throwable $e) {
   381	            // Fallback — return a placeholder
   382	        }
   383	
   384	        // Fallback SVG placeholder
   385	        return '<svg width="120" height="120" xmlns="http://www.w3.org/2000/svg"><rect width="120" height="120" fill="#eee"/><text x="60" y="60" text-anchor="middle" dy=".3em" font-size="10" fill="#999">QR</text></svg>';
   386	    }
   387	
   388	    /**
   389	     * Calculate card expiry date based on member's subscription.
   390	     */
   391	    protected function calculateExpiryDate(Member $member): string
   392	    {
   393	        $paidThroughYear = $member->paid_through_year ?? (int) date('Y');
   394	        return $paidThroughYear . '-12-31';
   395	    }
   396	
   397	    /**
   398	     * Get printing cost based on card type.
   399	     */
   400	    protected function getPrintingCost(string $cardType): float
   401	    {
   402	        $costs = [
   403	            'main' => 0.00,
   404	            'replacement' => (float) $this->getClubSetting('card_replacement_fee', 50),
   405	            'renewal' => (float) $this->getClubSetting('card_renewal_fee', 0),
   406	        ];
   407	
   408	        return $costs[$cardType] ?? 0.00;
   409	    }
   410	
   411	    /**
   412	     * Get status color for card display.
   413	     */
   414	    protected function getStatusColor(string $statusKey): string
   415	    {
   416	        $colors = [
   417	            'active' => '#22c55e',
   418	            'suspended' => '#f59e0b',
   419	            'frozen' => '#3b82f6',
   420	            'expelled' => '#ef4444',
   421	            'resigned' => '#6b7280',
   422	            'deceased' => '#1f2937',
   423	        ];
   424	
   425	        return $colors[$statusKey] ?? '#6b7280';
   426	    }
   427	
   428	    /**
   429	     * Helper to get club setting.
   430	     */
   431	    protected function getClubSetting(string $key, mixed $default = null): mixed
   432	    {
   433	        try {
   434	            $info = DB::table('club_info')->first();
   435	            return $info?->$key ?? $default;
   436	        } catch (\Throwable $e) {
   437	            return $default;
   438	        }
   439	    }
   440	
   441	    /**
   442	     * Get current fiscal year.
   443	     */
   444	    protected function getCurrentFiscalYear(): int
   445	    {
   446	        $startMonth = (int) ($this->getClubSetting('fiscal_year_start_month', 1));
   447	        $now = now();
   448	        return $now->month < $startMonth ? $now->year - 1 : $now->year;
   449	    }
   450	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [351]: app/Services/Cards/CardService.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [352/494]: app/Services/Disciplinary/PenaltyService.php
│ LANGUAGE: php | LINES: 283 | SIZE: 9983 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	declare(strict_types=1);
     4	
     5	namespace App\Services\Disciplinary;
     6	
     7	use App\Enums\PenaltyStatus;
     8	use App\Models\Penalty;
     9	use App\Models\PenaltyType;
    10	use App\Services\SequenceService;
    11	use Carbon\Carbon;
    12	use Illuminate\Database\Eloquent\Collection;
    13	use Illuminate\Support\Facades\Auth;
    14	use Illuminate\Support\Facades\DB;
    15	
    16	class PenaltyService
    17	{
    18	    public function __construct(
    19	        protected SequenceService $sequenceService,
    20	        protected SuspensionService $suspensionService,
    21	    ) {}
    22	
    23	    /**
    24	     * Create a penalty record.
    25	     */
    26	    public function createPenalty(array $data): Penalty
    27	    {
    28	        return DB::transaction(function () use ($data) {
    29	            $penaltyNumber = $this->sequenceService->nextValue('penalty_number');
    30	
    31	            $penaltyType = isset($data['penalty_type_id'])
    32	                ? PenaltyType::find($data['penalty_type_id'])
    33	                : null;
    34	
    35	            $penalty = Penalty::create([
    36	                'penalty_number'    => $penaltyNumber,
    37	                'violation_id'      => $data['violation_id'],
    38	                'member_id'         => $data['member_id'],
    39	                'penalty_type_id'   => $data['penalty_type_id'] ?? null,
    40	                'status'            => PenaltyStatus::ACTIVE,
    41	                'description'       => $data['description'] ?? $penaltyType?->description ?? null,
    42	                'imposed_date'      => $data['imposed_date'] ?? now(),
    43	                'effective_from'    => $data['effective_from'] ?? now(),
    44	                'effective_until'   => $data['effective_until'] ?? null,
    45	                'fine_amount'       => $data['fine_amount'] ?? $penaltyType?->default_fine_amount ?? 0,
    46	                'fine_paid'         => false,
    47	                'fine_paid_at'      => null,
    48	                'requires_suspension' => $data['requires_suspension'] ?? $penaltyType?->requires_suspension ?? false,
    49	                'suspension_days'     => $data['suspension_days'] ?? $penaltyType?->default_suspension_days ?? null,
    50	                'imposed_by'        => $data['imposed_by'] ?? Auth::id(),
    51	                'notes'             => $data['notes'] ?? null,
    52	                'board_decision_reference' => $data['board_decision_reference'] ?? null,
    53	            ]);
    54	
    55	            // Auto-create suspension if required
    56	            if ($penalty->requires_suspension && $penalty->suspension_days > 0) {
    57	                $this->suspensionService->createSuspension([
    58	                    'member_id'    => $penalty->member_id,
    59	                    'penalty_id'   => $penalty->id,
    60	                    'violation_id' => $penalty->violation_id,
    61	                    'start_date'   => $penalty->effective_from,
    62	                    'end_date'     => Carbon::parse($penalty->effective_from)->addDays($penalty->suspension_days),
    63	                    'reason'       => $penalty->description,
    64	                    'notes'        => "Auto-created from penalty #{$penaltyNumber}",
    65	                ]);
    66	            }
    67	
    68	            activity()
    69	                ->performedOn($penalty)
    70	                ->causedBy(Auth::user())
    71	                ->withProperties([
    72	                    'penalty_number' => $penaltyNumber,
    73	                    'fine_amount'    => $penalty->fine_amount,
    74	                ])
    75	                ->log('penalty_created');
    76	
    77	            return $penalty->fresh(['violation', 'member', 'penaltyType', 'imposedBy']);
    78	        });
    79	    }
    80	
    81	    /**
    82	     * Record fine payment for a penalty.
    83	     */
    84	    public function recordFinePayment(Penalty $penalty, array $data = []): Penalty
    85	    {
    86	        if ($penalty->fine_amount <= 0) {
    87	            throw new \InvalidArgumentException('This penalty has no fine to pay.');
    88	        }
    89	
    90	        if ($penalty->fine_paid) {
    91	            throw new \InvalidArgumentException('Fine has already been paid.');
    92	        }
    93	
    94	        return DB::transaction(function () use ($penalty, $data) {
    95	            $penalty->update([
    96	                'fine_paid'       => true,
    97	                'fine_paid_at'    => now(),
    98	                'fine_receipt_id' => $data['receipt_id'] ?? null,
    99	            ]);
   100	
   101	            // Check if penalty can be resolved
   102	            $this->checkAndResolve($penalty);
   103	
   104	            activity()
   105	                ->performedOn($penalty)
   106	                ->causedBy(Auth::user())
   107	                ->log('fine_payment_recorded');
   108	
   109	            return $penalty->fresh();
   110	        });
   111	    }
   112	
   113	    /**
   114	     * Enforce a penalty (mark it as being actively enforced).
   115	     */
   116	    public function enforce(Penalty $penalty): Penalty
   117	    {
   118	        if ($penalty->status !== PenaltyStatus::ACTIVE) {
   119	            throw new \App\Exceptions\InvalidStatusTransitionException(
   120	                "Cannot enforce penalty in status: {$penalty->status->value}"
   121	            );
   122	        }
   123	
   124	        return DB::transaction(function () use ($penalty) {
   125	            $penalty->update(['status' => PenaltyStatus::ENFORCED]);
   126	
   127	            activity()
   128	                ->performedOn($penalty)
   129	                ->causedBy(Auth::user())
   130	                ->log('penalty_enforced');
   131	
   132	            return $penalty->fresh();
   133	        });
   134	    }
   135	
   136	    /**
   137	     * Complete/resolve a penalty.
   138	     */
   139	    public function complete(Penalty $penalty): Penalty
   140	    {
   141	        return DB::transaction(function () use ($penalty) {
   142	            $penalty->update([
   143	                'status'       => PenaltyStatus::COMPLETED,
   144	                'completed_at' => now(),
   145	            ]);
   146	
   147	            activity()
   148	                ->performedOn($penalty)
   149	                ->causedBy(Auth::user())
   150	                ->log('penalty_completed');
   151	
   152	            return $penalty->fresh();
   153	        });
   154	    }
   155	
   156	    /**
   157	     * Waive a penalty (forgiveness by board).
   158	     */
   159	    public function waive(Penalty $penalty, array $data = []): Penalty
   160	    {
   161	        if (in_array($penalty->status, [PenaltyStatus::COMPLETED, PenaltyStatus::WAIVED, PenaltyStatus::OVERTURNED])) {
   162	            throw new \App\Exceptions\InvalidStatusTransitionException(
   163	                "Cannot waive penalty in status: {$penalty->status->value}"
   164	            );
   165	        }
   166	
   167	        return DB::transaction(function () use ($penalty, $data) {
   168	            $penalty->update([
   169	                'status'      => PenaltyStatus::WAIVED,
   170	                'waived_by'   => Auth::id(),
   171	                'waived_at'   => now(),
   172	                'waive_reason' => $data['reason'] ?? null,
   173	                'board_decision_reference' => $data['board_decision_reference'] ?? $penalty->board_decision_reference,
   174	                'completed_at' => now(),
   175	            ]);
   176	
   177	            // Also end any related suspension
   178	            if ($penalty->requires_suspension) {
   179	                $this->suspensionService->endSuspensionsForPenalty($penalty->id, 'Penalty waived');
   180	            }
   181	
   182	            activity()
   183	                ->performedOn($penalty)
   184	                ->causedBy(Auth::user())
   185	                ->withProperties(['reason' => $data['reason'] ?? null])
   186	                ->log('penalty_waived');
   187	
   188	            return $penalty->fresh();
   189	        });
   190	    }
   191	
   192	    /**
   193	     * Overturn a penalty (on appeal).
   194	     */
   195	    public function overturn(Penalty $penalty, array $data = []): Penalty
   196	    {
   197	        return DB::transaction(function () use ($penalty, $data) {
   198	            $penalty->update([
   199	                'status'        => PenaltyStatus::OVERTURNED,
   200	                'overturned_by' => Auth::id(),
   201	                'overturned_at' => now(),
   202	                'overturn_reason' => $data['reason'] ?? null,
   203	                'completed_at'    => now(),
   204	            ]);
   205	
   206	            // End related suspension
   207	            if ($penalty->requires_suspension) {
   208	                $this->suspensionService->endSuspensionsForPenalty($penalty->id, 'Penalty overturned on appeal');
   209	            }
   210	
   211	            activity()
   212	                ->performedOn($penalty)
   213	                ->causedBy(Auth::user())
   214	                ->withProperties(['reason' => $data['reason'] ?? null])
   215	                ->log('penalty_overturned');
   216	
   217	            return $penalty->fresh();
   218	        });
   219	    }
   220	
   221	    /**
   222	     * Get all active penalties for a member.
   223	     */
   224	    public function getActivePenalties(int $memberId): Collection
   225	    {
   226	        return Penalty::where('member_id', $memberId)
   227	            ->whereIn('status', [PenaltyStatus::ACTIVE, PenaltyStatus::ENFORCED])
   228	            ->with(['violation', 'penaltyType'])
   229	            ->orderByDesc('imposed_date')
   230	            ->get();
   231	    }
   232	
   233	    /**
   234	     * Get total unpaid fines for a member.
   235	     */
   236	    public function getTotalUnpaidFines(int $memberId): float
   237	    {
   238	        return (float) Penalty::where('member_id', $memberId)
   239	            ->whereIn('status', [PenaltyStatus::ACTIVE, PenaltyStatus::ENFORCED])
   240	            ->where('fine_paid', false)
   241	            ->where('fine_amount', '>', 0)
   242	            ->sum('fine_amount');
   243	    }
   244	
   245	    /**
   246	     * Check if penalty conditions are all met and auto-resolve.
   247	     */
   248	    protected function checkAndResolve(Penalty $penalty): void
   249	    {
   250	        $penalty->refresh();
   251	
   252	        $fineResolved = $penalty->fine_amount <= 0 || $penalty->fine_paid;
   253	        $suspensionResolved = !$penalty->requires_suspension
   254	            || ($penalty->effective_until && Carbon::parse($penalty->effective_until)->isPast());
   255	
   256	        if ($fineResolved && $suspensionResolved) {
   257	            $this->complete($penalty);
   258	        }
   259	    }
   260	
   261	    /**
   262	     * Auto-complete expired penalties (called by scheduler).
   263	     */
   264	    public function autoCompleteExpiredPenalties(): int
   265	    {
   266	        $count = 0;
   267	
   268	        $expired = Penalty::whereIn('status', [PenaltyStatus::ACTIVE, PenaltyStatus::ENFORCED])
   269	            ->whereNotNull('effective_until')
   270	            ->where('effective_until', '<=', now())
   271	            ->where(function ($q) {
   272	                $q->where('fine_amount', '<=', 0)
   273	                  ->orWhere('fine_paid', true);
   274	            })
   275	            ->get();
   276	
   277	        foreach ($expired as $penalty) {
   278	            $this->complete($penalty);
   279	            $count++;
   280	        }
   281	
   282	        return $count;
   283	    }
   284	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [352]: app/Services/Disciplinary/PenaltyService.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [353/494]: app/Services/Disciplinary/SuspensionService.php
│ LANGUAGE: php | LINES: 264 | SIZE: 8887 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	declare(strict_types=1);
     4	
     5	namespace App\Services\Disciplinary;
     6	
     7	use App\Enums\MembershipStatus;
     8	use App\Enums\SuspensionStatus;
     9	use App\Models\Member;
    10	use App\Models\MemberSuspension;
    11	use Carbon\Carbon;
    12	use Illuminate\Database\Eloquent\Collection;
    13	use Illuminate\Support\Facades\Auth;
    14	use Illuminate\Support\Facades\DB;
    15	
    16	class SuspensionService
    17	{
    18	    /**
    19	     * Create a suspension record.
    20	     */
    21	    public function createSuspension(array $data): MemberSuspension
    22	    {
    23	        return DB::transaction(function () use ($data) {
    24	            $suspension = MemberSuspension::create([
    25	                'member_id'    => $data['member_id'],
    26	                'penalty_id'   => $data['penalty_id'] ?? null,
    27	                'violation_id' => $data['violation_id'] ?? null,
    28	                'status'       => SuspensionStatus::ACTIVE,
    29	                'start_date'   => $data['start_date'] ?? now(),
    30	                'end_date'     => $data['end_date'] ?? null,
    31	                'reason'       => $data['reason'] ?? null,
    32	                'suspended_by' => $data['suspended_by'] ?? Auth::id(),
    33	                'notes'        => $data['notes'] ?? null,
    34	                'board_decision_reference' => $data['board_decision_reference'] ?? null,
    35	            ]);
    36	
    37	            // Update member status to suspended
    38	            $member = Member::find($data['member_id']);
    39	            if ($member && $member->membership_status !== MembershipStatus::SUSPENDED) {
    40	                $member->update([
    41	                    'membership_status'          => MembershipStatus::SUSPENDED,
    42	                    'previous_membership_status' => $member->membership_status,
    43	                ]);
    44	            }
    45	
    46	            activity()
    47	                ->performedOn($suspension)
    48	                ->causedBy(Auth::user())
    49	                ->withProperties([
    50	                    'member_id'  => $data['member_id'],
    51	                    'start_date' => $suspension->start_date,
    52	                    'end_date'   => $suspension->end_date,
    53	                ])
    54	                ->log('suspension_created');
    55	
    56	            return $suspension->fresh(['member', 'penalty', 'violation', 'suspendedBy']);
    57	        });
    58	    }
    59	
    60	    /**
    61	     * Lift (end) a suspension early.
    62	     */
    63	    public function liftSuspension(MemberSuspension $suspension, array $data = []): MemberSuspension
    64	    {
    65	        if ($suspension->status !== SuspensionStatus::ACTIVE) {
    66	            throw new \App\Exceptions\InvalidStatusTransitionException(
    67	                "Cannot lift suspension in status: {$suspension->status->value}"
    68	            );
    69	        }
    70	
    71	        return DB::transaction(function () use ($suspension, $data) {
    72	            $suspension->update([
    73	                'status'      => SuspensionStatus::LIFTED,
    74	                'lifted_by'   => Auth::id(),
    75	                'lifted_at'   => now(),
    76	                'lift_reason'  => $data['reason'] ?? null,
    77	                'actual_end_date' => now(),
    78	            ]);
    79	
    80	            // Restore member status if no other active suspensions
    81	            $this->restoreMemberStatusIfClear($suspension->member_id);
    82	
    83	            activity()
    84	                ->performedOn($suspension)
    85	                ->causedBy(Auth::user())
    86	                ->withProperties(['reason' => $data['reason'] ?? null])
    87	                ->log('suspension_lifted');
    88	
    89	            return $suspension->fresh();
    90	        });
    91	    }
    92	
    93	    /**
    94	     * Expire a suspension that has reached its end date.
    95	     */
    96	    public function expireSuspension(MemberSuspension $suspension): MemberSuspension
    97	    {
    98	        if ($suspension->status !== SuspensionStatus::ACTIVE) {
    99	            return $suspension;
   100	        }
   101	
   102	        return DB::transaction(function () use ($suspension) {
   103	            $suspension->update([
   104	                'status'          => SuspensionStatus::EXPIRED,
   105	                'actual_end_date' => $suspension->end_date ?? now(),
   106	            ]);
   107	
   108	            $this->restoreMemberStatusIfClear($suspension->member_id);
   109	
   110	            activity()
   111	                ->performedOn($suspension)
   112	                ->log('suspension_expired');
   113	
   114	            return $suspension->fresh();
   115	        });
   116	    }
   117	
   118	    /**
   119	     * Extend a suspension's end date.
   120	     */
   121	    public function extendSuspension(MemberSuspension $suspension, Carbon $newEndDate, ?string $reason = null): MemberSuspension
   122	    {
   123	        if ($suspension->status !== SuspensionStatus::ACTIVE) {
   124	            throw new \App\Exceptions\InvalidStatusTransitionException(
   125	                "Cannot extend suspension in status: {$suspension->status->value}"
   126	            );
   127	        }
   128	
   129	        return DB::transaction(function () use ($suspension, $newEndDate, $reason) {
   130	            $oldEndDate = $suspension->end_date;
   131	
   132	            $suspension->update([
   133	                'end_date'          => $newEndDate,
   134	                'extension_reason'  => $reason,
   135	                'extended_by'       => Auth::id(),
   136	                'extended_at'       => now(),
   137	            ]);
   138	
   139	            activity()
   140	                ->performedOn($suspension)
   141	                ->causedBy(Auth::user())
   142	                ->withProperties([
   143	                    'old_end_date' => $oldEndDate,
   144	                    'new_end_date' => $newEndDate->toDateString(),
   145	                    'reason'       => $reason,
   146	                ])
   147	                ->log('suspension_extended');
   148	
   149	            return $suspension->fresh();
   150	        });
   151	    }
   152	
   153	    /**
   154	     * End all suspensions linked to a specific penalty.
   155	     */
   156	    public function endSuspensionsForPenalty(int $penaltyId, ?string $reason = null): int
   157	    {
   158	        $suspensions = MemberSuspension::where('penalty_id', $penaltyId)
   159	            ->where('status', SuspensionStatus::ACTIVE)
   160	            ->get();
   161	
   162	        $count = 0;
   163	        foreach ($suspensions as $suspension) {
   164	            $this->liftSuspension($suspension, ['reason' => $reason ?? 'Linked penalty resolved']);
   165	            $count++;
   166	        }
   167	
   168	        return $count;
   169	    }
   170	
   171	    /**
   172	     * Check if member is currently suspended.
   173	     */
   174	    public function isSuspended(int $memberId): bool
   175	    {
   176	        return MemberSuspension::where('member_id', $memberId)
   177	            ->where('status', SuspensionStatus::ACTIVE)
   178	            ->where('start_date', '<=', now())
   179	            ->where(function ($q) {
   180	                $q->whereNull('end_date')
   181	                  ->orWhere('end_date', '>=', now());
   182	            })
   183	            ->exists();
   184	    }
   185	
   186	    /**
   187	     * Get active suspensions for a member.
   188	     */
   189	    public function getActiveSuspensions(int $memberId): Collection
   190	    {
   191	        return MemberSuspension::where('member_id', $memberId)
   192	            ->where('status', SuspensionStatus::ACTIVE)
   193	            ->with(['penalty', 'violation', 'suspendedBy'])
   194	            ->orderByDesc('start_date')
   195	            ->get();
   196	    }
   197	
   198	    /**
   199	     * Get suspension history for a member.
   200	     */
   201	    public function getSuspensionHistory(int $memberId): Collection
   202	    {
   203	        return MemberSuspension::where('member_id', $memberId)
   204	            ->with(['penalty', 'violation', 'suspendedBy'])
   205	            ->orderByDesc('start_date')
   206	            ->get();
   207	    }
   208	
   209	    /**
   210	     * Auto-expire all suspensions past their end date. Called by scheduler.
   211	     */
   212	    public function autoExpireSuspensions(): int
   213	    {
   214	        $count = 0;
   215	
   216	        $expired = MemberSuspension::where('status', SuspensionStatus::ACTIVE)
   217	            ->whereNotNull('end_date')
   218	            ->where('end_date', '<', now())
   219	            ->get();
   220	
   221	        foreach ($expired as $suspension) {
   222	            $this->expireSuspension($suspension);
   223	            $count++;
   224	        }
   225	
   226	        return $count;
   227	    }
   228	
   229	    /**
   230	     * If member has no more active suspensions, restore their previous status.
   231	     */
   232	    protected function restoreMemberStatusIfClear(int $memberId): void
   233	    {
   234	        if (!$this->isSuspended($memberId)) {
   235	            $member = Member::find($memberId);
   236	            if ($member && $member->membership_status === MembershipStatus::SUSPENDED) {
   237	                $previousStatus = $member->previous_membership_status ?? MembershipStatus::ACTIVE;
   238	                $member->update([
   239	                    'membership_status'          => $previousStatus,
   240	                    'previous_membership_status' => null,
   241	                ]);
   242	            }
   243	        }
   244	    }
   245	
   246	    /**
   247	     * Get remaining suspension days for a member.
   248	     */
   249	    public function getRemainingSuspensionDays(int $memberId): int
   250	    {
   251	        $activeSuspension = MemberSuspension::where('member_id', $memberId)
   252	            ->where('status', SuspensionStatus::ACTIVE)
   253	            ->whereNotNull('end_date')
   254	            ->orderByDesc('end_date')
   255	            ->first();
   256	
   257	        if (!$activeSuspension || !$activeSuspension->end_date) {
   258	            return 0;
   259	        }
   260	
   261	        $remaining = now()->diffInDays(Carbon::parse($activeSuspension->end_date), false);
   262	
   263	        return max(0, (int) $remaining);
   264	    }
   265	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [353]: app/Services/Disciplinary/SuspensionService.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [354/494]: app/Services/Disciplinary/ViolationService.php
│ LANGUAGE: php | LINES: 315 | SIZE: 11198 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	declare(strict_types=1);
     4	
     5	namespace App\Services\Disciplinary;
     6	
     7	use App\Enums\PenaltyStatus;
     8	use App\Enums\SuspensionStatus;
     9	use App\Enums\ViolationSeverity;
    10	use App\Enums\ViolationStatus;
    11	use App\Models\Member;
    12	use App\Models\MemberSuspension;
    13	use App\Models\Penalty;
    14	use App\Models\Violation;
    15	use App\Models\ViolationType;
    16	use App\Services\SequenceService;
    17	use Carbon\Carbon;
    18	use Illuminate\Database\Eloquent\Collection;
    19	use Illuminate\Support\Facades\DB;
    20	use Illuminate\Support\Facades\Auth;
    21	
    22	class ViolationService
    23	{
    24	    public function __construct(
    25	        protected SequenceService $sequenceService,
    26	        protected PenaltyService $penaltyService,
    27	        protected SuspensionService $suspensionService,
    28	    ) {}
    29	
    30	    /**
    31	     * Report a new violation against a member.
    32	     */
    33	    public function reportViolation(array $data): Violation
    34	    {
    35	        return DB::transaction(function () use ($data) {
    36	            $violationNumber = $this->sequenceService->nextValue('violation_number');
    37	
    38	            $violationType = isset($data['violation_type_id'])
    39	                ? ViolationType::find($data['violation_type_id'])
    40	                : null;
    41	
    42	            $violation = Violation::create([
    43	                'violation_number'   => $violationNumber,
    44	                'member_id'          => $data['member_id'],
    45	                'violation_type_id'  => $data['violation_type_id'] ?? null,
    46	                'severity'           => $data['severity'] ?? $violationType?->default_severity ?? ViolationSeverity::MINOR,
    47	                'status'             => ViolationStatus::REPORTED,
    48	                'title'              => $data['title'],
    49	                'description'        => $data['description'] ?? null,
    50	                'violation_date'     => $data['violation_date'] ?? now(),
    51	                'violation_location' => $data['violation_location'] ?? null,
    52	                'reported_by'        => $data['reported_by'] ?? Auth::id(),
    53	                'reported_at'        => now(),
    54	                'witnesses'          => $data['witnesses'] ?? null,
    55	                'evidence_notes'     => $data['evidence_notes'] ?? null,
    56	                'notes'              => $data['notes'] ?? null,
    57	            ]);
    58	
    59	            activity()
    60	                ->performedOn($violation)
    61	                ->causedBy(Auth::user())
    62	                ->withProperties(['violation_number' => $violationNumber])
    63	                ->log('violation_reported');
    64	
    65	            return $violation->fresh(['member', 'violationType', 'reporter']);
    66	        });
    67	    }
    68	
    69	    /**
    70	     * Start investigation on a violation.
    71	     */
    72	    public function startInvestigation(Violation $violation, array $data = []): Violation
    73	    {
    74	        $this->assertStatus($violation, [ViolationStatus::REPORTED]);
    75	
    76	        return DB::transaction(function () use ($violation, $data) {
    77	            $violation->update([
    78	                'status'            => ViolationStatus::UNDER_INVESTIGATION,
    79	                'investigated_by'   => $data['investigated_by'] ?? Auth::id(),
    80	                'investigation_started_at' => now(),
    81	                'investigation_notes'      => $data['investigation_notes'] ?? null,
    82	            ]);
    83	
    84	            activity()
    85	                ->performedOn($violation)
    86	                ->causedBy(Auth::user())
    87	                ->log('investigation_started');
    88	
    89	            return $violation->fresh();
    90	        });
    91	    }
    92	
    93	    /**
    94	     * Complete investigation with findings.
    95	     */
    96	    public function completeInvestigation(Violation $violation, array $data): Violation
    97	    {
    98	        $this->assertStatus($violation, [ViolationStatus::UNDER_INVESTIGATION]);
    99	
   100	        return DB::transaction(function () use ($violation, $data) {
   101	            $updateData = [
   102	                'status'                    => ViolationStatus::INVESTIGATION_COMPLETE,
   103	                'investigation_notes'       => $data['investigation_notes'] ?? $violation->investigation_notes,
   104	                'investigation_findings'    => $data['investigation_findings'] ?? null,
   105	                'investigation_completed_at' => now(),
   106	            ];
   107	
   108	            // Allow severity re-assessment after investigation
   109	            if (isset($data['severity'])) {
   110	                $updateData['severity'] = $data['severity'];
   111	            }
   112	
   113	            $violation->update($updateData);
   114	
   115	            activity()
   116	                ->performedOn($violation)
   117	                ->causedBy(Auth::user())
   118	                ->withProperties(['findings' => $data['investigation_findings'] ?? null])
   119	                ->log('investigation_completed');
   120	
   121	            return $violation->fresh();
   122	        });
   123	    }
   124	
   125	    /**
   126	     * Escalate violation to board review.
   127	     */
   128	    public function escalateToBoard(Violation $violation, array $data = []): Violation
   129	    {
   130	        $this->assertStatus($violation, [
   131	            ViolationStatus::INVESTIGATION_COMPLETE,
   132	            ViolationStatus::REPORTED,
   133	        ]);
   134	
   135	        return DB::transaction(function () use ($violation, $data) {
   136	            $violation->update([
   137	                'status'              => ViolationStatus::PENDING_BOARD_REVIEW,
   138	                'escalated_to_board'  => true,
   139	                'escalation_date'     => now(),
   140	                'escalation_reason'   => $data['escalation_reason'] ?? null,
   141	            ]);
   142	
   143	            activity()
   144	                ->performedOn($violation)
   145	                ->causedBy(Auth::user())
   146	                ->log('escalated_to_board');
   147	
   148	            return $violation->fresh();
   149	        });
   150	    }
   151	
   152	    /**
   153	     * Record board decision on violation.
   154	     */
   155	    public function recordBoardDecision(Violation $violation, array $data): Violation
   156	    {
   157	        $this->assertStatus($violation, [ViolationStatus::PENDING_BOARD_REVIEW]);
   158	
   159	        return DB::transaction(function () use ($violation, $data) {
   160	            $newStatus = ($data['board_approved'] ?? false)
   161	                ? ViolationStatus::BOARD_REVIEWED
   162	                : ViolationStatus::DISMISSED;
   163	
   164	            $violation->update([
   165	                'status'                  => $newStatus,
   166	                'board_decision_notes'    => $data['board_decision_notes'] ?? null,
   167	                'board_decision_date'     => $data['board_decision_date'] ?? now(),
   168	                'board_decision_reference' => $data['board_decision_reference'] ?? null,
   169	            ]);
   170	
   171	            activity()
   172	                ->performedOn($violation)
   173	                ->causedBy(Auth::user())
   174	                ->withProperties(['board_approved' => $data['board_approved'] ?? false])
   175	                ->log('board_decision_recorded');
   176	
   177	            return $violation->fresh();
   178	        });
   179	    }
   180	
   181	    /**
   182	     * Impose penalty on a confirmed violation.
   183	     */
   184	    public function imposePenalty(Violation $violation, array $penaltyData): Penalty
   185	    {
   186	        $this->assertStatus($violation, [
   187	            ViolationStatus::INVESTIGATION_COMPLETE,
   188	            ViolationStatus::BOARD_REVIEWED,
   189	        ]);
   190	
   191	        return DB::transaction(function () use ($violation, $penaltyData) {
   192	            $penaltyData['violation_id'] = $violation->id;
   193	            $penaltyData['member_id'] = $violation->member_id;
   194	
   195	            $penalty = $this->penaltyService->createPenalty($penaltyData);
   196	
   197	            // Update violation status
   198	            $violation->update([
   199	                'status'      => ViolationStatus::PENALTY_IMPOSED,
   200	                'resolved_at' => now(),
   201	            ]);
   202	
   203	            activity()
   204	                ->performedOn($violation)
   205	                ->causedBy(Auth::user())
   206	                ->withProperties(['penalty_id' => $penalty->id])
   207	                ->log('penalty_imposed');
   208	
   209	            return $penalty;
   210	        });
   211	    }
   212	
   213	    /**
   214	     * Dismiss a violation (no action taken).
   215	     */
   216	    public function dismiss(Violation $violation, array $data = []): Violation
   217	    {
   218	        $this->assertStatus($violation, [
   219	            ViolationStatus::REPORTED,
   220	            ViolationStatus::UNDER_INVESTIGATION,
   221	            ViolationStatus::INVESTIGATION_COMPLETE,
   222	            ViolationStatus::PENDING_BOARD_REVIEW,
   223	            ViolationStatus::BOARD_REVIEWED,
   224	        ]);
   225	
   226	        return DB::transaction(function () use ($violation, $data) {
   227	            $violation->update([
   228	                'status'           => ViolationStatus::DISMISSED,
   229	                'dismissal_reason' => $data['dismissal_reason'] ?? null,
   230	                'dismissed_by'     => Auth::id(),
   231	                'dismissed_at'     => now(),
   232	                'resolved_at'      => now(),
   233	            ]);
   234	
   235	            activity()
   236	                ->performedOn($violation)
   237	                ->causedBy(Auth::user())
   238	                ->withProperties(['reason' => $data['dismissal_reason'] ?? null])
   239	                ->log('violation_dismissed');
   240	
   241	            return $violation->fresh();
   242	        });
   243	    }
   244	
   245	    /**
   246	     * Get violation history for a member.
   247	     */
   248	    public function getMemberViolationHistory(int $memberId): Collection
   249	    {
   250	        return Violation::where('member_id', $memberId)
   251	            ->with(['violationType', 'penalties', 'reporter'])
   252	            ->orderByDesc('violation_date')
   253	            ->get();
   254	    }
   255	
   256	    /**
   257	     * Get active (unresolved) violations for a member.
   258	     */
   259	    public function getActiveViolations(int $memberId): Collection
   260	    {
   261	        return Violation::where('member_id', $memberId)
   262	            ->whereNotIn('status', [
   263	                ViolationStatus::DISMISSED,
   264	                ViolationStatus::RESOLVED,
   265	                ViolationStatus::APPEALED_OVERTURNED,
   266	            ])
   267	            ->with(['violationType', 'penalties'])
   268	            ->orderByDesc('violation_date')
   269	            ->get();
   270	    }
   271	
   272	    /**
   273	     * Count violations by severity for a member within a period.
   274	     */
   275	    public function countBySeverity(int $memberId, ?Carbon $since = null): array
   276	    {
   277	        $query = Violation::where('member_id', $memberId)
   278	            ->whereNotIn('status', [ViolationStatus::DISMISSED, ViolationStatus::APPEALED_OVERTURNED]);
   279	
   280	        if ($since) {
   281	            $query->where('violation_date', '>=', $since);
   282	        }
   283	
   284	        return $query->get()
   285	            ->groupBy('severity')
   286	            ->map(fn ($group) => $group->count())
   287	            ->toArray();
   288	    }
   289	
   290	    /**
   291	     * Check if member has any active violations (used by other modules).
   292	     */
   293	    public function hasActiveViolations(int $memberId): bool
   294	    {
   295	        return Violation::where('member_id', $memberId)
   296	            ->whereNotIn('status', [
   297	                ViolationStatus::DISMISSED,
   298	                ViolationStatus::RESOLVED,
   299	                ViolationStatus::APPEALED_OVERTURNED,
   300	            ])
   301	            ->exists();
   302	    }
   303	
   304	    /**
   305	     * Assert violation is in one of the expected statuses.
   306	     */
   307	    protected function assertStatus(Violation $violation, array $allowedStatuses): void
   308	    {
   309	        if (!in_array($violation->status, $allowedStatuses)) {
   310	            $allowed = implode(', ', array_map(fn ($s) => $s->value, $allowedStatuses));
   311	            throw new \App\Exceptions\InvalidStatusTransitionException(
   312	                "Violation #{$violation->violation_number} is '{$violation->status->value}'. Expected one of: {$allowed}"
   313	            );
   314	        }
   315	    }
   316	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [354]: app/Services/Disciplinary/ViolationService.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [355/494]: app/Services/Documents/DocumentService.php
│ LANGUAGE: php | LINES: 500 | SIZE: 17472 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Services\Documents;
     4	
     5	use App\Enums\AuditAction;
     6	use App\Models\DocumentRequirement;
     7	use App\Models\DocumentType;
     8	use App\Models\Member;
     9	use App\Models\MemberDependent;
    10	use App\Models\MemberDocument;
    11	use Illuminate\Http\UploadedFile;
    12	use Illuminate\Support\Facades\Auth;
    13	use Illuminate\Support\Facades\DB;
    14	use Illuminate\Support\Facades\Storage;
    15	use Illuminate\Support\Str;
    16	
    17	class DocumentService
    18	{
    19	    /**
    20	     * Upload a document for a member.
    21	     */
    22	    public function uploadDocument(
    23	        Member $member,
    24	        int $documentTypeId,
    25	        UploadedFile $file,
    26	        ?int $dependentId = null,
    27	        ?string $expiryDate = null,
    28	        ?string $notes = null
    29	    ): MemberDocument {
    30	        return DB::transaction(function () use ($member, $documentTypeId, $file, $dependentId, $expiryDate, $notes) {
    31	            $documentType = DocumentType::findOrFail($documentTypeId);
    32	
    33	            // Validate file
    34	            $this->validateFile($file, $documentType);
    35	
    36	            // Archive any existing document of the same type
    37	            MemberDocument::where('member_id', $member->id)
    38	                ->where('document_type_id', $documentTypeId)
    39	                ->where('dependent_id', $dependentId)
    40	                ->where('is_archived', false)
    41	                ->update([
    42	                    'is_archived' => true,
    43	                    'updated_by' => Auth::id(),
    44	                ]);
    45	
    46	            // Store file
    47	            $folder = $dependentId
    48	                ? "documents/members/{$member->id}/dependents/{$dependentId}"
    49	                : "documents/members/{$member->id}";
    50	
    51	            $filename = $documentType->code . '_' . Str::random(8) . '.' . $file->getClientOriginalExtension();
    52	            $path = $file->storeAs($folder, $filename, 'public');
    53	
    54	            $document = MemberDocument::create([
    55	                'member_id' => $member->id,
    56	                'dependent_id' => $dependentId,
    57	                'document_type_id' => $documentTypeId,
    58	                'file_path' => $path,
    59	                'original_filename' => $file->getClientOriginalName(),
    60	                'file_size_kb' => (int) ceil($file->getSize() / 1024),
    61	                'mime_type' => $file->getMimeType(),
    62	                'is_verified' => false,
    63	                'expiry_date' => $expiryDate,
    64	                'notes' => $notes,
    65	                'created_by' => Auth::id(),
    66	                'updated_by' => Auth::id(),
    67	            ]);
    68	
    69	            activity()
    70	                ->performedOn($document)
    71	                ->causedBy(Auth::user())
    72	                ->withProperties([
    73	                    'document_type' => $documentType->name_ar,
    74	                    'member_id' => $member->id,
    75	                    'original_filename' => $file->getClientOriginalName(),
    76	                ])
    77	                ->log(AuditAction::CREATE->value);
    78	
    79	            return $document;
    80	        });
    81	    }
    82	
    83	    /**
    84	     * Verify a document.
    85	     */
    86	    public function verifyDocument(MemberDocument $document, ?string $verificationNotes = null): MemberDocument
    87	    {
    88	        $document->update([
    89	            'is_verified' => true,
    90	            'verified_by' => Auth::id(),
    91	            'verified_at' => now(),
    92	            'verification_notes' => $verificationNotes,
    93	            'updated_by' => Auth::id(),
    94	        ]);
    95	
    96	        activity()
    97	            ->performedOn($document)
    98	            ->causedBy(Auth::user())
    99	            ->withProperties([
   100	                'action' => 'verified',
   101	                'notes' => $verificationNotes,
   102	            ])
   103	            ->log('document_verified');
   104	
   105	        return $document->fresh();
   106	    }
   107	
   108	    /**
   109	     * Reject/unverify a document.
   110	     */
   111	    public function rejectDocument(MemberDocument $document, string $reason): MemberDocument
   112	    {
   113	        $document->update([
   114	            'is_verified' => false,
   115	            'verified_by' => Auth::id(),
   116	            'verified_at' => now(),
   117	            'verification_notes' => 'مرفوض: ' . $reason,
   118	            'updated_by' => Auth::id(),
   119	        ]);
   120	
   121	        activity()
   122	            ->performedOn($document)
   123	            ->causedBy(Auth::user())
   124	            ->withProperties([
   125	                'action' => 'rejected',
   126	                'reason' => $reason,
   127	            ])
   128	            ->log('document_rejected');
   129	
   130	        return $document->fresh();
   131	    }
   132	
   133	    /**
   134	     * Delete (archive) a document.
   135	     */
   136	    public function archiveDocument(MemberDocument $document): bool
   137	    {
   138	        $document->update([
   139	            'is_archived' => true,
   140	            'updated_by' => Auth::id(),
   141	        ]);
   142	
   143	        activity()
   144	            ->performedOn($document)
   145	            ->causedBy(Auth::user())
   146	            ->withProperties([
   147	                'action' => 'archived',
   148	                'file_path' => $document->file_path,
   149	            ])
   150	            ->log(AuditAction::ARCHIVE->value);
   151	
   152	        return true;
   153	    }
   154	
   155	    /**
   156	     * Permanently delete a document and its file.
   157	     */
   158	    public function deleteDocument(MemberDocument $document): bool
   159	    {
   160	        return DB::transaction(function () use ($document) {
   161	            if ($document->file_path && Storage::disk('public')->exists($document->file_path)) {
   162	                Storage::disk('public')->delete($document->file_path);
   163	            }
   164	
   165	            activity()
   166	                ->performedOn($document)
   167	                ->causedBy(Auth::user())
   168	                ->withProperties([
   169	                    'action' => 'deleted_permanently',
   170	                    'file_path' => $document->file_path,
   171	                    'original_filename' => $document->original_filename,
   172	                ])
   173	                ->log(AuditAction::DELETE->value);
   174	
   175	            return $document->forceDelete();
   176	        });
   177	    }
   178	
   179	    /**
   180	     * Get document checklist for a member at a specific workflow stage.
   181	     */
   182	    public function getDocumentChecklist(Member $member, ?string $workflowStage = null): array
   183	    {
   184	        $stage = $workflowStage ?? $this->inferWorkflowStage($member);
   185	
   186	        // Get required documents for this stage & membership type
   187	        $requirements = DocumentRequirement::with('documentType')
   188	            ->where('is_active', true)
   189	            ->where('workflow_stage', $stage)
   190	            ->where(function ($q) use ($member) {
   191	                $q->whereNull('membership_type_id')
   192	                    ->orWhere('membership_type_id', $member->membership_type_id);
   193	            })
   194	            ->orderBy('sort_order')
   195	            ->get();
   196	
   197	        // Get member's existing documents
   198	        $existingDocs = MemberDocument::where('member_id', $member->id)
   199	            ->whereNull('dependent_id')
   200	            ->where('is_archived', false)
   201	            ->get()
   202	            ->keyBy('document_type_id');
   203	
   204	        $checklist = [];
   205	        $totalRequired = 0;
   206	        $totalCompleted = 0;
   207	
   208	        foreach ($requirements as $requirement) {
   209	            $docType = $requirement->documentType;
   210	            if (!$docType || !$docType->is_active) {
   211	                continue;
   212	            }
   213	
   214	            $existingDoc = $existingDocs->get($docType->id);
   215	            $status = 'missing';
   216	            if ($existingDoc) {
   217	                $status = $existingDoc->is_verified ? 'verified' : 'uploaded';
   218	                if ($existingDoc->expiry_date && $existingDoc->expiry_date < now()->toDateString()) {
   219	                    $status = 'expired';
   220	                }
   221	            }
   222	
   223	            $isComplete = in_array($status, ['verified', 'uploaded']);
   224	            if ($requirement->is_mandatory) {
   225	                $totalRequired++;
   226	                if ($isComplete) {
   227	                    $totalCompleted++;
   228	                }
   229	            }
   230	
   231	            $checklist[] = [
   232	                'requirement_id' => $requirement->id,
   233	                'document_type_id' => $docType->id,
   234	                'document_type_name_ar' => $docType->name_ar,
   235	                'document_type_name_en' => $docType->name_en,
   236	                'document_type_code' => $docType->code,
   237	                'is_mandatory' => $requirement->is_mandatory,
   238	                'status' => $status,
   239	                'status_label' => $this->getStatusLabel($status),
   240	                'status_icon' => $this->getStatusIcon($status),
   241	                'status_color' => $this->getStatusColor($status),
   242	                'existing_document' => $existingDoc,
   243	                'accepted_extensions' => $docType->accepted_extensions,
   244	                'max_file_size_kb' => $docType->max_file_size_kb,
   245	            ];
   246	        }
   247	
   248	        return [
   249	            'member' => $member,
   250	            'workflow_stage' => $stage,
   251	            'checklist' => $checklist,
   252	            'total_required' => $totalRequired,
   253	            'total_completed' => $totalCompleted,
   254	            'completion_percentage' => $totalRequired > 0 ? round(($totalCompleted / $totalRequired) * 100) : 100,
   255	            'is_complete' => $totalCompleted >= $totalRequired,
   256	        ];
   257	    }
   258	
   259	    /**
   260	     * Get document checklist for a dependent.
   261	     */
   262	    public function getDependentDocumentChecklist(MemberDependent $dependent): array
   263	    {
   264	        $member = $dependent->member;
   265	
   266	        $requirements = DocumentRequirement::with('documentType')
   267	            ->where('is_active', true)
   268	            ->whereHas('documentType', function ($q) {
   269	                $q->whereIn('applies_to', ['dependent', 'both']);
   270	            })
   271	            ->where(function ($q) use ($member) {
   272	                $q->whereNull('membership_type_id')
   273	                    ->orWhere('membership_type_id', $member->membership_type_id);
   274	            })
   275	            ->orderBy('sort_order')
   276	            ->get();
   277	
   278	        $existingDocs = MemberDocument::where('member_id', $member->id)
   279	            ->where('dependent_id', $dependent->id)
   280	            ->where('is_archived', false)
   281	            ->get()
   282	            ->keyBy('document_type_id');
   283	
   284	        $checklist = [];
   285	        $totalRequired = 0;
   286	        $totalCompleted = 0;
   287	
   288	        foreach ($requirements as $requirement) {
   289	            $docType = $requirement->documentType;
   290	            if (!$docType || !$docType->is_active) {
   291	                continue;
   292	            }
   293	
   294	            $existingDoc = $existingDocs->get($docType->id);
   295	            $status = 'missing';
   296	            if ($existingDoc) {
   297	                $status = $existingDoc->is_verified ? 'verified' : 'uploaded';
   298	                if ($existingDoc->expiry_date && $existingDoc->expiry_date < now()->toDateString()) {
   299	                    $status = 'expired';
   300	                }
   301	            }
   302	
   303	            $isComplete = in_array($status, ['verified', 'uploaded']);
   304	            if ($requirement->is_mandatory) {
   305	                $totalRequired++;
   306	                if ($isComplete) {
   307	                    $totalCompleted++;
   308	                }
   309	            }
   310	
   311	            $checklist[] = [
   312	                'requirement_id' => $requirement->id,
   313	                'document_type_id' => $docType->id,
   314	                'document_type_name_ar' => $docType->name_ar,
   315	                'document_type_name_en' => $docType->name_en,
   316	                'document_type_code' => $docType->code,
   317	                'is_mandatory' => $requirement->is_mandatory,
   318	                'status' => $status,
   319	                'status_label' => $this->getStatusLabel($status),
   320	                'status_icon' => $this->getStatusIcon($status),
   321	                'status_color' => $this->getStatusColor($status),
   322	                'existing_document' => $existingDoc,
   323	                'accepted_extensions' => $docType->accepted_extensions,
   324	                'max_file_size_kb' => $docType->max_file_size_kb,
   325	            ];
   326	        }
   327	
   328	        return [
   329	            'dependent' => $dependent,
   330	            'member' => $member,
   331	            'checklist' => $checklist,
   332	            'total_required' => $totalRequired,
   333	            'total_completed' => $totalCompleted,
   334	            'completion_percentage' => $totalRequired > 0 ? round(($totalCompleted / $totalRequired) * 100) : 100,
   335	            'is_complete' => $totalCompleted >= $totalRequired,
   336	        ];
   337	    }
   338	
   339	    /**
   340	     * Check if all mandatory documents are complete for a member.
   341	     */
   342	    public function isMemberDocumentComplete(Member $member, ?string $workflowStage = null): bool
   343	    {
   344	        $checklist = $this->getDocumentChecklist($member, $workflowStage);
   345	        return $checklist['is_complete'];
   346	    }
   347	
   348	    /**
   349	     * Get documents expiring within N days.
   350	     */
   351	    public function getExpiringDocuments(int $daysAhead = 30): \Illuminate\Database\Eloquent\Collection
   352	    {
   353	        return MemberDocument::with(['member', 'documentType'])
   354	            ->where('is_archived', false)
   355	            ->where('is_verified', true)
   356	            ->whereNotNull('expiry_date')
   357	            ->whereBetween('expiry_date', [now()->toDateString(), now()->addDays($daysAhead)->toDateString()])
   358	            ->orderBy('expiry_date')
   359	            ->get();
   360	    }
   361	
   362	    /**
   363	     * Get all expired documents.
   364	     */
   365	    public function getExpiredDocuments(): \Illuminate\Database\Eloquent\Collection
   366	    {
   367	        return MemberDocument::with(['member', 'documentType'])
   368	            ->where('is_archived', false)
   369	            ->whereNotNull('expiry_date')
   370	            ->where('expiry_date', '<', now()->toDateString())
   371	            ->orderBy('expiry_date')
   372	            ->get();
   373	    }
   374	
   375	    /**
   376	     * Get compliance summary for all members.
   377	     */
   378	    public function getComplianceSummary(): array
   379	    {
   380	        $totalMembers = Member::where('is_archived', false)->count();
   381	
   382	        $membersWithAllDocs = 0;
   383	        $membersWithMissing = 0;
   384	        $membersWithUnverified = 0;
   385	
   386	        $members = Member::where('is_archived', false)->get();
   387	
   388	        foreach ($members as $member) {
   389	            $checklist = $this->getDocumentChecklist($member);
   390	            if ($checklist['is_complete']) {
   391	                $membersWithAllDocs++;
   392	            } else {
   393	                $membersWithMissing++;
   394	            }
   395	
   396	            $hasUnverified = collect($checklist['checklist'])->contains(fn($item) => $item['status'] === 'uploaded');
   397	            if ($hasUnverified) {
   398	                $membersWithUnverified++;
   399	            }
   400	        }
   401	
   402	        return [
   403	            'total_members' => $totalMembers,
   404	            'members_complete' => $membersWithAllDocs,
   405	            'members_incomplete' => $membersWithMissing,
   406	            'members_pending_verification' => $membersWithUnverified,
   407	            'compliance_rate' => $totalMembers > 0 ? round(($membersWithAllDocs / $totalMembers) * 100, 1) : 0,
   408	        ];
   409	    }
   410	
   411	    /**
   412	     * Validate uploaded file against document type constraints.
   413	     */
   414	    protected function validateFile(UploadedFile $file, DocumentType $documentType): void
   415	    {
   416	        // Check file size
   417	        if ($documentType->max_file_size_kb) {
   418	            $fileSizeKb = (int) ceil($file->getSize() / 1024);
   419	            if ($fileSizeKb > $documentType->max_file_size_kb) {
   420	                throw new \InvalidArgumentException(
   421	                    "حجم الملف ({$fileSizeKb} كيلوبايت) يتجاوز الحد الأقصى المسموح ({$documentType->max_file_size_kb} كيلوبايت)."
   422	                );
   423	            }
   424	        }
   425	
   426	        // Check extension
   427	        if ($documentType->accepted_extensions) {
   428	            $extension = strtolower($file->getClientOriginalExtension());
   429	            $accepted = array_map('trim', explode(',', strtolower($documentType->accepted_extensions)));
   430	
   431	            if (!in_array($extension, $accepted)) {
   432	                throw new \InvalidArgumentException(
   433	                    "امتداد الملف ({$extension}) غير مسموح. الامتدادات المسموحة: {$documentType->accepted_extensions}"
   434	                );
   435	            }
   436	        }
   437	    }
   438	
   439	    /**
   440	     * Infer workflow stage from member status.
   441	     */
   442	    protected function inferWorkflowStage(Member $member): string
   443	    {
   444	        $statusKey = $member->status->status_key ?? 'active';
   445	
   446	        $stageMap = [
   447	            'pending_application' => 'application',
   448	            'pending_review' => 'review',
   449	            'pending_interview' => 'interview',
   450	            'pending_board_approval' => 'board_approval',
   451	            'pending_payment' => 'payment',
   452	            'active' => 'active',
   453	            'frozen' => 'active',
   454	            'suspended' => 'active',
   455	        ];
   456	
   457	        return $stageMap[$statusKey] ?? 'active';
   458	    }
   459	
   460	    /**
   461	     * Get status label.
   462	     */
   463	    protected function getStatusLabel(string $status): string
   464	    {
   465	        return match ($status) {
   466	            'verified' => '✅ تم التحقق',
   467	            'uploaded' => '⏳ في انتظار التحقق',
   468	            'missing' => '❌ مفقود',
   469	            'expired' => '⚠️ منتهي الصلاحية',
   470	            default => 'غير معروف',
   471	        };
   472	    }
   473	
   474	    /**
   475	     * Get status icon.
   476	     */
   477	    protected function getStatusIcon(string $status): string
   478	    {
   479	        return match ($status) {
   480	            'verified' => 'heroicon-o-check-circle',
   481	            'uploaded' => 'heroicon-o-clock',
   482	            'missing' => 'heroicon-o-x-circle',
   483	            'expired' => 'heroicon-o-exclamation-triangle',
   484	            default => 'heroicon-o-question-mark-circle',
   485	        };
   486	    }
   487	
   488	    /**
   489	     * Get status color.
   490	     */
   491	    protected function getStatusColor(string $status): string
   492	    {
   493	        return match ($status) {
   494	            'verified' => 'success',
   495	            'uploaded' => 'warning',
   496	            'missing' => 'danger',
   497	            'expired' => 'danger',
   498	            default => 'gray',
   499	        };
   500	    }
   501	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [355]: app/Services/Documents/DocumentService.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [356/494]: app/Services/Financial/CashRegisterService.php
│ LANGUAGE: php | LINES: 170 | SIZE: 6173 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Services\Financial;
     4	
     5	use App\Models\CashRegister;
     6	use App\Models\CashRegisterTransaction;
     7	use App\Models\Receipt;
     8	use App\Enums\CashRegisterStatus;
     9	use Illuminate\Support\Facades\DB;
    10	use Illuminate\Support\Facades\Auth;
    11	
    12	class CashRegisterService
    13	{
    14	    /**
    15	     * Open a new cash register session.
    16	     */
    17	    public function openRegister(float $openingBalance = 0, ?string $notes = null): CashRegister
    18	    {
    19	        return DB::transaction(function () use ($openingBalance, $notes) {
    20	            // Check if there's already an open register for this user
    21	            $existingOpen = CashRegister::where('opened_by', Auth::id())
    22	                ->where('status', CashRegisterStatus::OPEN->value)
    23	                ->first();
    24	
    25	            if ($existingOpen) {
    26	                throw new \RuntimeException('يوجد صندوق مفتوح بالفعل لهذا المستخدم.');
    27	            }
    28	
    29	            $register = CashRegister::create([
    30	                'opened_by' => Auth::id(),
    31	                'opened_at' => now(),
    32	                'opening_balance' => $openingBalance,
    33	                'current_balance' => $openingBalance,
    34	                'total_receipts' => 0,
    35	                'total_amount_collected' => 0,
    36	                'total_voided' => 0,
    37	                'receipt_count' => 0,
    38	                'void_count' => 0,
    39	                'status' => CashRegisterStatus::OPEN->value,
    40	                'opening_notes' => $notes,
    41	            ]);
    42	
    43	            activity()
    44	                ->performedOn($register)
    45	                ->causedBy(Auth::user())
    46	                ->withProperties(['opening_balance' => $openingBalance])
    47	                ->log('cash_register_opened');
    48	
    49	            return $register;
    50	        });
    51	    }
    52	
    53	    /**
    54	     * Close a cash register session.
    55	     */
    56	    public function closeRegister(CashRegister $register, float $actualClosingBalance, ?string $notes = null): CashRegister
    57	    {
    58	        return DB::transaction(function () use ($register, $actualClosingBalance, $notes) {
    59	            if ($register->status !== CashRegisterStatus::OPEN->value) {
    60	                throw new \RuntimeException('هذا الصندوق مغلق بالفعل.');
    61	            }
    62	
    63	            $expectedBalance = $register->current_balance;
    64	            $variance = $actualClosingBalance - $expectedBalance;
    65	
    66	            $register->update([
    67	                'closed_by' => Auth::id(),
    68	                'closed_at' => now(),
    69	                'closing_balance' => $actualClosingBalance,
    70	                'expected_balance' => $expectedBalance,
    71	                'variance' => $variance,
    72	                'status' => CashRegisterStatus::CLOSED->value,
    73	                'closing_notes' => $notes,
    74	            ]);
    75	
    76	            activity()
    77	                ->performedOn($register)
    78	                ->causedBy(Auth::user())
    79	                ->withProperties([
    80	                    'expected_balance' => $expectedBalance,
    81	                    'actual_balance' => $actualClosingBalance,
    82	                    'variance' => $variance,
    83	                ])
    84	                ->log('cash_register_closed');
    85	
    86	            return $register->fresh();
    87	        });
    88	    }
    89	
    90	    /**
    91	     * Get the currently open register for the authenticated user.
    92	     */
    93	    public function getOpenRegister(): ?CashRegister
    94	    {
    95	        return CashRegister::where('opened_by', Auth::id())
    96	            ->where('status', CashRegisterStatus::OPEN->value)
    97	            ->first();
    98	    }
    99	
   100	    /**
   101	     * Get ANY open register (for system-level operations).
   102	     */
   103	    public function getAnyOpenRegister(): ?CashRegister
   104	    {
   105	        return CashRegister::where('status', CashRegisterStatus::OPEN->value)
   106	            ->orderBy('opened_at', 'desc')
   107	            ->first();
   108	    }
   109	
   110	    /**
   111	     * Add a transaction to the register (receipt payment).
   112	     */
   113	    public function addTransaction(CashRegister $register, float $amount, Receipt $receipt): void
   114	    {
   115	        CashRegisterTransaction::create([
   116	            'cash_register_id' => $register->id,
   117	            'receipt_id' => $receipt->id,
   118	            'transaction_type' => 'receipt',
   119	            'amount' => $amount,
   120	            'running_balance' => $register->current_balance + $amount,
   121	            'notes' => "إيصال رقم {$receipt->receipt_number}",
   122	            'created_by' => Auth::id(),
   123	        ]);
   124	
   125	        $register->increment('current_balance', $amount);
   126	        $register->increment('total_amount_collected', $amount);
   127	        $register->increment('receipt_count');
   128	    }
   129	
   130	    /**
   131	     * Reverse a transaction (void receipt).
   132	     */
   133	    public function reverseTransaction(CashRegister $register, float $amount, Receipt $receipt): void
   134	    {
   135	        CashRegisterTransaction::create([
   136	            'cash_register_id' => $register->id,
   137	            'receipt_id' => $receipt->id,
   138	            'transaction_type' => 'void',
   139	            'amount' => -$amount,
   140	            'running_balance' => $register->current_balance - $amount,
   141	            'notes' => "إلغاء إيصال رقم {$receipt->receipt_number}",
   142	            'created_by' => Auth::id(),
   143	        ]);
   144	
   145	        $register->decrement('current_balance', $amount);
   146	        $register->increment('total_voided', $amount);
   147	        $register->increment('void_count');
   148	    }
   149	
   150	    /**
   151	     * Get register summary for the day.
   152	     */
   153	    public function getDailySummary(?string $date = null): array
   154	    {
   155	        $date = $date ?? now()->toDateString();
   156	
   157	        $registers = CashRegister::whereDate('opened_at', $date)->get();
   158	
   159	        return [
   160	            'date' => $date,
   161	            'registers_opened' => $registers->count(),
   162	            'registers_closed' => $registers->where('status', CashRegisterStatus::CLOSED->value)->count(),
   163	            'total_opening_balance' => $registers->sum('opening_balance'),
   164	            'total_collected' => $registers->sum('total_amount_collected'),
   165	            'total_voided' => $registers->sum('total_voided'),
   166	            'total_receipts' => $registers->sum('receipt_count'),
   167	            'total_voids' => $registers->sum('void_count'),
   168	            'total_variance' => $registers->where('status', CashRegisterStatus::CLOSED->value)->sum('variance'),
   169	        ];
   170	    }
   171	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [356]: app/Services/Financial/CashRegisterService.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [357/494]: app/Services/Financial/FeeCalculationService.php
│ LANGUAGE: php | LINES: 331 | SIZE: 12585 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Services\Financial;
     4	
     5	use App\Models\Member;
     6	use App\Models\MemberDependent;
     7	use App\Models\MembershipType;
     8	use App\Models\FeeStructure;
     9	use App\Models\Subscription;
    10	use App\Models\SystemSetting;
    11	use App\Enums\FeeCategory;
    12	use App\Enums\SubscriptionStatus;
    13	use Carbon\Carbon;
    14	use Illuminate\Support\Collection;
    15	use Illuminate\Support\Facades\DB;
    16	
    17	class FeeCalculationService
    18	{
    19	    /**
    20	     * Calculate all fees for a new membership application.
    21	     */
    22	    public function calculateNewMembershipFees(
    23	        int $membershipTypeId,
    24	        int $dependentCount = 0,
    25	        array $dependentAges = [],
    26	        ?string $boardOfferId = null
    27	    ): array {
    28	        $type = MembershipType::findOrFail($membershipTypeId);
    29	        $fees = [];
    30	        $totalAmount = 0;
    31	
    32	        // 1. Application form fee (استمارة)
    33	        $applicationFee = $this->getFee(FeeCategory::APPLICATION_FORM, $membershipTypeId);
    34	        if ($applicationFee > 0) {
    35	            $fees[] = [
    36	                'category' => FeeCategory::APPLICATION_FORM->value,
    37	                'category_label' => FeeCategory::APPLICATION_FORM->getLabel(),
    38	                'description_ar' => 'رسم استمارة العضوية',
    39	                'description_en' => 'Application Form Fee',
    40	                'amount' => $applicationFee,
    41	                'quantity' => 1,
    42	                'subtotal' => $applicationFee,
    43	            ];
    44	            $totalAmount += $applicationFee;
    45	        }
    46	
    47	        // 2. Membership value (قيمة العضوية)
    48	        $membershipValue = $this->getFee(FeeCategory::MEMBERSHIP_VALUE, $membershipTypeId);
    49	        if ($membershipValue > 0) {
    50	            $fees[] = [
    51	                'category' => FeeCategory::MEMBERSHIP_VALUE->value,
    52	                'category_label' => FeeCategory::MEMBERSHIP_VALUE->getLabel(),
    53	                'description_ar' => 'قيمة العضوية - ' . $type->name_ar,
    54	                'description_en' => 'Membership Value - ' . $type->name_en,
    55	                'amount' => $membershipValue,
    56	                'quantity' => 1,
    57	                'subtotal' => $membershipValue,
    58	            ];
    59	            $totalAmount += $membershipValue;
    60	        }
    61	
    62	        // 3. Annual subscription (الاشتراك السنوي)
    63	        $annualSub = $this->getFee(FeeCategory::ANNUAL_SUBSCRIPTION, $membershipTypeId);
    64	        if ($annualSub > 0) {
    65	            $fees[] = [
    66	                'category' => FeeCategory::ANNUAL_SUBSCRIPTION->value,
    67	                'category_label' => FeeCategory::ANNUAL_SUBSCRIPTION->getLabel(),
    68	                'description_ar' => 'الاشتراك السنوي ' . now()->year,
    69	                'description_en' => 'Annual Subscription ' . now()->year,
    70	                'amount' => $annualSub,
    71	                'quantity' => 1,
    72	                'subtotal' => $annualSub,
    73	            ];
    74	            $totalAmount += $annualSub;
    75	        }
    76	
    77	        // 4. Dependent fees
    78	        foreach ($dependentAges as $index => $age) {
    79	            $depFee = $this->calculateDependentFee($membershipTypeId, $age);
    80	            if ($depFee > 0) {
    81	                $fees[] = [
    82	                    'category' => FeeCategory::DEPENDENT_FEE->value,
    83	                    'category_label' => FeeCategory::DEPENDENT_FEE->getLabel(),
    84	                    'description_ar' => 'رسم تابع (' . ($index + 1) . ') - عمر ' . $age . ' سنة',
    85	                    'description_en' => 'Dependent Fee (' . ($index + 1) . ') - Age ' . $age,
    86	                    'amount' => $depFee,
    87	                    'quantity' => 1,
    88	                    'subtotal' => $depFee,
    89	                ];
    90	                $totalAmount += $depFee;
    91	            }
    92	        }
    93	
    94	        // 5. Card fees
    95	        $cardFee = $this->getFee(FeeCategory::CARD_ISSUANCE, $membershipTypeId);
    96	        $cardCount = 1 + count($dependentAges); // member + dependents
    97	        if ($cardFee > 0) {
    98	            $fees[] = [
    99	                'category' => FeeCategory::CARD_ISSUANCE->value,
   100	                'category_label' => FeeCategory::CARD_ISSUANCE->getLabel(),
   101	                'description_ar' => 'رسم إصدار كارنيهات',
   102	                'description_en' => 'Card Issuance Fees',
   103	                'amount' => $cardFee,
   104	                'quantity' => $cardCount,
   105	                'subtotal' => $cardFee * $cardCount,
   106	            ];
   107	            $totalAmount += ($cardFee * $cardCount);
   108	        }
   109	
   110	        // Apply board offer discount if applicable
   111	        $discount = 0;
   112	        $discountDescription = null;
   113	        if ($boardOfferId) {
   114	            $offerResult = $this->applyBoardOfferDiscount($boardOfferId, $totalAmount, $fees);
   115	            $discount = $offerResult['discount'];
   116	            $discountDescription = $offerResult['description'];
   117	        }
   118	
   119	        // VAT calculation
   120	        $vatRate = $this->getVatRate();
   121	        $taxableAmount = $totalAmount - $discount;
   122	        $vatAmount = $vatRate > 0 ? round($taxableAmount * ($vatRate / 100), 2) : 0;
   123	
   124	        return [
   125	            'fees' => $fees,
   126	            'subtotal' => $totalAmount,
   127	            'discount' => $discount,
   128	            'discount_description' => $discountDescription,
   129	            'taxable_amount' => $taxableAmount,
   130	            'vat_rate' => $vatRate,
   131	            'vat_amount' => $vatAmount,
   132	            'total' => $taxableAmount + $vatAmount,
   133	        ];
   134	    }
   135	
   136	    /**
   137	     * Calculate annual subscription amount for a member.
   138	     */
   139	    public function calculateAnnualSubscription(Member $member, int $year): array
   140	    {
   141	        $fees = [];
   142	        $totalAmount = 0;
   143	
   144	        // Main member subscription
   145	        $annualFee = $this->getFee(FeeCategory::ANNUAL_SUBSCRIPTION, $member->membership_type_id);
   146	        $fees[] = [
   147	            'category' => FeeCategory::ANNUAL_SUBSCRIPTION->value,
   148	            'category_label' => FeeCategory::ANNUAL_SUBSCRIPTION->getLabel(),
   149	            'description_ar' => 'اشتراك سنوي ' . $year . ' - ' . $member->full_name_ar,
   150	            'description_en' => 'Annual Subscription ' . $year . ' - ' . $member->full_name_en,
   151	            'amount' => $annualFee,
   152	            'quantity' => 1,
   153	            'subtotal' => $annualFee,
   154	        ];
   155	        $totalAmount += $annualFee;
   156	
   157	        // Active dependent subscriptions
   158	        $activeDependents = $member->dependents()
   159	            ->where('status', 'active')
   160	            ->where('is_archived', false)
   161	            ->get();
   162	
   163	        foreach ($activeDependents as $dependent) {
   164	            $age = Carbon::parse($dependent->date_of_birth)->age;
   165	            $depSubFee = $this->calculateDependentSubscriptionFee($member->membership_type_id, $age);
   166	
   167	            if ($depSubFee > 0) {
   168	                $fees[] = [
   169	                    'category' => FeeCategory::DEPENDENT_SUBSCRIPTION->value,
   170	                    'category_label' => FeeCategory::DEPENDENT_SUBSCRIPTION->getLabel(),
   171	                    'description_ar' => 'اشتراك تابع ' . $year . ' - ' . $dependent->full_name_ar,
   172	                    'description_en' => 'Dependent Subscription ' . $year . ' - ' . ($dependent->full_name_en ?? $dependent->full_name_ar),
   173	                    'amount' => $depSubFee,
   174	                    'quantity' => 1,
   175	                    'subtotal' => $depSubFee,
   176	                ];
   177	                $totalAmount += $depSubFee;
   178	            }
   179	        }
   180	
   181	        return [
   182	            'fees' => $fees,
   183	            'year' => $year,
   184	            'member_id' => $member->id,
   185	            'subtotal' => $totalAmount,
   186	            'total' => $totalAmount,
   187	        ];
   188	    }
   189	
   190	    /**
   191	     * Calculate late payment penalty.
   192	     */
   193	    public function calculateLatePenalty(Subscription $subscription): float
   194	    {
   195	        if ($subscription->due_date >= now()) {
   196	            return 0;
   197	        }
   198	
   199	        $daysOverdue = now()->diffInDays($subscription->due_date);
   200	        $penaltyRate = (float) $this->getSetting('late_penalty_rate_percent', 0);
   201	        $penaltyMax = (float) $this->getSetting('late_penalty_max_amount', 0);
   202	        $gracePeriodDays = (int) $this->getSetting('grace_period_days', 30);
   203	
   204	        if ($daysOverdue <= $gracePeriodDays) {
   205	            return 0;
   206	        }
   207	
   208	        $effectiveOverdueDays = $daysOverdue - $gracePeriodDays;
   209	        $penalty = round($subscription->amount_remaining * ($penaltyRate / 100) * ($effectiveOverdueDays / 30), 2);
   210	
   211	        if ($penaltyMax > 0 && $penalty > $penaltyMax) {
   212	            $penalty = $penaltyMax;
   213	        }
   214	
   215	        return $penalty;
   216	    }
   217	
   218	    /**
   219	     * Get a fee amount from the fee_structures table.
   220	     */
   221	    public function getFee(FeeCategory $category, int $membershipTypeId, ?int $year = null): float
   222	    {
   223	        $year = $year ?? now()->year;
   224	
   225	        $fee = FeeStructure::where('fee_category', $category->value)
   226	            ->where('membership_type_id', $membershipTypeId)
   227	            ->where('effective_from', '<=', now())
   228	            ->where(function ($q) {
   229	                $q->whereNull('effective_to')
   230	                    ->orWhere('effective_to', '>=', now());
   231	            })
   232	            ->where('is_active', true)
   233	            ->where('is_archived', false)
   234	            ->orderByDesc('effective_from')
   235	            ->first();
   236	
   237	        return $fee ? (float) $fee->amount : 0;
   238	    }
   239	
   240	    /**
   241	     * Calculate dependent fee based on age bracket.
   242	     */
   243	    public function calculateDependentFee(int $membershipTypeId, int $age): float
   244	    {
   245	        $feeStructure = FeeStructure::where('fee_category', FeeCategory::DEPENDENT_FEE->value)
   246	            ->where('membership_type_id', $membershipTypeId)
   247	            ->where('age_from', '<=', $age)
   248	            ->where(function ($q) use ($age) {
   249	                $q->whereNull('age_to')
   250	                    ->orWhere('age_to', '>=', $age);
   251	            })
   252	            ->where('is_active', true)
   253	            ->where('is_archived', false)
   254	            ->where('effective_from', '<=', now())
   255	            ->where(function ($q) {
   256	                $q->whereNull('effective_to')
   257	                    ->orWhere('effective_to', '>=', now());
   258	            })
   259	            ->orderByDesc('effective_from')
   260	            ->first();
   261	
   262	        return $feeStructure ? (float) $feeStructure->amount : 0;
   263	    }
   264	
   265	    /**
   266	     * Calculate dependent annual subscription fee based on age.
   267	     */
   268	    public function calculateDependentSubscriptionFee(int $membershipTypeId, int $age): float
   269	    {
   270	        $feeStructure = FeeStructure::where('fee_category', FeeCategory::DEPENDENT_SUBSCRIPTION->value)
   271	            ->where('membership_type_id', $membershipTypeId)
   272	            ->where('age_from', '<=', $age)
   273	            ->where(function ($q) use ($age) {
   274	                $q->whereNull('age_to')
   275	                    ->orWhere('age_to', '>=', $age);
   276	            })
   277	            ->where('is_active', true)
   278	            ->where('is_archived', false)
   279	            ->where('effective_from', '<=', now())
   280	            ->where(function ($q) {
   281	                $q->whereNull('effective_to')
   282	                    ->orWhere('effective_to', '>=', now());
   283	            })
   284	            ->orderByDesc('effective_from')
   285	            ->first();
   286	
   287	        return $feeStructure ? (float) $feeStructure->amount : 0;
   288	    }
   289	
   290	    /**
   291	     * Apply board offer discount.
   292	     */
   293	    protected function applyBoardOfferDiscount(string $boardOfferId, float $totalAmount, array $fees): array
   294	    {
   295	        $offer = DB::table('board_offers')
   296	            ->where('id', $boardOfferId)
   297	            ->where('status', 'active')
   298	            ->where('valid_from', '<=', now())
   299	            ->where('valid_to', '>=', now())
   300	            ->where('is_archived', false)
   301	            ->first();
   302	
   303	        if (!$offer) {
   304	            return ['discount' => 0, 'description' => null];
   305	        }
   306	
   307	        $discount = 0;
   308	        if ($offer->discount_type === 'percentage') {
   309	            $discount = round($totalAmount * ($offer->discount_value / 100), 2);
   310	            if ($offer->max_discount_amount && $discount > $offer->max_discount_amount) {
   311	                $discount = (float) $offer->max_discount_amount;
   312	            }
   313	        } elseif ($offer->discount_type === 'fixed') {
   314	            $discount = (float) $offer->discount_value;
   315	        }
   316	
   317	        return [
   318	            'discount' => min($discount, $totalAmount),
   319	            'description' => $offer->name_ar ?? $offer->description_ar ?? 'خصم عرض مجلس الإدارة',
   320	        ];
   321	    }
   322	
   323	    protected function getVatRate(): float
   324	    {
   325	        return (float) $this->getSetting('vat_rate_percent', 0);
   326	    }
   327	
   328	    protected function getSetting(string $key, mixed $default = null): mixed
   329	    {
   330	        return SystemSetting::where('setting_key', $key)->value('setting_value') ?? $default;
   331	    }
   332	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [357]: app/Services/Financial/FeeCalculationService.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [358/494]: app/Services/Financial/InstallmentService.php
│ LANGUAGE: php | LINES: 199 | SIZE: 8246 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Services\Financial;
     4	
     5	use App\Models\InstallmentPlan;
     6	use App\Models\Installment;
     7	use App\Models\Subscription;
     8	use App\Models\Member;
     9	use App\Enums\InstallmentPlanStatus;
    10	use App\Enums\InstallmentStatus;
    11	use Illuminate\Support\Facades\DB;
    12	use Illuminate\Support\Facades\Auth;
    13	use Carbon\Carbon;
    14	
    15	class InstallmentService
    16	{
    17	    public function __construct(
    18	        protected ReceiptService $receiptService,
    19	    ) {}
    20	
    21	    /**
    22	     * Create an installment plan for a subscription or any payable amount.
    23	     */
    24	    public function createPlan(array $data): InstallmentPlan
    25	    {
    26	        return DB::transaction(function () use ($data) {
    27	            $totalAmount = (float) $data['total_amount'];
    28	            $downPayment = (float) ($data['down_payment'] ?? 0);
    29	            $numberOfInstallments = (int) $data['number_of_installments'];
    30	            $intervalDays = (int) ($data['interval_days'] ?? 30);
    31	            $startDate = Carbon::parse($data['start_date'] ?? now());
    32	
    33	            if ($numberOfInstallments < 1 || $numberOfInstallments > 12) {
    34	                throw new \RuntimeException('عدد الأقساط يجب أن يكون بين 1 و 12.');
    35	            }
    36	
    37	            $amountToInstall = $totalAmount - $downPayment;
    38	            if ($amountToInstall <= 0) {
    39	                throw new \RuntimeException('المبلغ المتبقي للتقسيط يجب أن يكون أكبر من صفر.');
    40	            }
    41	
    42	            $installmentAmount = floor($amountToInstall / $numberOfInstallments * 100) / 100;
    43	            $lastInstallmentAmount = $amountToInstall - ($installmentAmount * ($numberOfInstallments - 1));
    44	
    45	            $plan = InstallmentPlan::create([
    46	                'member_id' => $data['member_id'],
    47	                'subscription_id' => $data['subscription_id'] ?? null,
    48	                'total_amount' => $totalAmount,
    49	                'down_payment' => $downPayment,
    50	                'installment_amount' => $installmentAmount,
    51	                'number_of_installments' => $numberOfInstallments,
    52	                'interval_days' => $intervalDays,
    53	                'start_date' => $startDate,
    54	                'status' => InstallmentPlanStatus::ACTIVE->value,
    55	                'approved_by' => $data['approved_by'] ?? Auth::id(),
    56	                'notes_ar' => $data['notes_ar'] ?? null,
    57	                'notes_en' => $data['notes_en'] ?? null,
    58	                'created_by' => Auth::id(),
    59	            ]);
    60	
    61	            // Generate individual installments
    62	            for ($i = 1; $i <= $numberOfInstallments; $i++) {
    63	                $dueDate = $startDate->copy()->addDays($intervalDays * $i);
    64	                $amount = ($i === $numberOfInstallments) ? $lastInstallmentAmount : $installmentAmount;
    65	
    66	                Installment::create([
    67	                    'installment_plan_id' => $plan->id,
    68	                    'installment_number' => $i,
    69	                    'amount' => $amount,
    70	                    'amount_paid' => 0,
    71	                    'amount_remaining' => $amount,
    72	                    'due_date' => $dueDate,
    73	                    'status' => InstallmentStatus::PENDING->value,
    74	                ]);
    75	            }
    76	
    77	            // Process down payment if any
    78	            if ($downPayment > 0) {
    79	                $this->receiptService->createReceipt([
    80	                    'member_id' => $data['member_id'],
    81	                    'subscription_id' => $data['subscription_id'] ?? null,
    82	                    'installment_plan_id' => $plan->id,
    83	                    'fee_category' => $data['fee_category'] ?? 'annual_subscription',
    84	                    'items' => [[
    85	                        'category' => $data['fee_category'] ?? 'annual_subscription',
    86	                        'description_ar' => 'دفعة مقدمة - خطة تقسيط',
    87	                        'description_en' => 'Down Payment - Installment Plan',
    88	                        'amount' => $downPayment,
    89	                        'quantity' => 1,
    90	                    ]],
    91	                    'amount_paid' => $downPayment,
    92	                    'payment_method' => $data['payment_method'] ?? 'cash',
    93	                    'notes_ar' => 'دفعة مقدمة لخطة التقسيط',
    94	                ]);
    95	            }
    96	
    97	            activity()
    98	                ->performedOn($plan)
    99	                ->causedBy(Auth::user())
   100	                ->withProperties([
   101	                    'total_amount' => $totalAmount,
   102	                    'down_payment' => $downPayment,
   103	                    'installments' => $numberOfInstallments,
   104	                ])
   105	                ->log('installment_plan_created');
   106	
   107	            return $plan->fresh(['installments']);
   108	        });
   109	    }
   110	
   111	    /**
   112	     * Pay an installment.
   113	     */
   114	    public function payInstallment(Installment $installment, float $amount, array $paymentData = []): array
   115	    {
   116	        return DB::transaction(function () use ($installment, $amount, $paymentData) {
   117	            if ($installment->status === InstallmentStatus::PAID->value) {
   118	                throw new \RuntimeException('هذا القسط مدفوع بالفعل.');
   119	            }
   120	
   121	            if ($amount > $installment->amount_remaining) {
   122	                throw new \RuntimeException(
   123	                    "المبلغ ({$amount}) أكبر من المتبقي للقسط ({$installment->amount_remaining})."
   124	                );
   125	            }
   126	
   127	            $plan = $installment->plan;
   128	
   129	            $receipt = $this->receiptService->createReceipt([
   130	                'member_id' => $plan->member_id,
   131	                'subscription_id' => $plan->subscription_id,
   132	                'installment_plan_id' => $plan->id,
   133	                'installment_id' => $installment->id,
   134	                'fee_category' => $paymentData['fee_category'] ?? 'annual_subscription',
   135	                'items' => [[
   136	                    'category' => $paymentData['fee_category'] ?? 'annual_subscription',
   137	                    'description_ar' => "سداد القسط رقم {$installment->installment_number}",
   138	                    'description_en' => "Installment #{$installment->installment_number} Payment",
   139	                    'amount' => $amount,
   140	                    'quantity' => 1,
   141	                ]],
   142	                'amount_paid' => $amount,
   143	                'payment_method' => $paymentData['payment_method'] ?? 'cash',
   144	                'payment_reference' => $paymentData['payment_reference'] ?? null,
   145	                'notes_ar' => "سداد القسط رقم {$installment->installment_number} من خطة التقسيط",
   146	            ]);
   147	
   148	            return [
   149	                'receipt' => $receipt,
   150	                'installment' => $installment->fresh(),
   151	                'plan' => $plan->fresh(['installments']),
   152	            ];
   153	        });
   154	    }
   155	
   156	    /**
   157	     * Mark overdue installments.
   158	     */
   159	    public function markOverdueInstallments(): int
   160	    {
   161	        return Installment::whereIn('status', [
   162	                InstallmentStatus::PENDING->value,
   163	                InstallmentStatus::PARTIALLY_PAID->value,
   164	            ])
   165	            ->where('due_date', '<', now())
   166	            ->update(['status' => InstallmentStatus::OVERDUE->value]);
   167	    }
   168	
   169	    /**
   170	     * Cancel an installment plan.
   171	     */
   172	    public function cancelPlan(InstallmentPlan $plan, string $reason): InstallmentPlan
   173	    {
   174	        return DB::transaction(function () use ($plan, $reason) {
   175	            $plan->update([
   176	                'status' => InstallmentPlanStatus::CANCELLED->value,
   177	                'cancellation_reason' => $reason,
   178	                'cancelled_by' => Auth::id(),
   179	                'cancelled_at' => now(),
   180	            ]);
   181	
   182	            // Mark remaining unpaid installments as cancelled
   183	            $plan->installments()
   184	                ->whereIn('status', [
   185	                    InstallmentStatus::PENDING->value,
   186	                    InstallmentStatus::PARTIALLY_PAID->value,
   187	                    InstallmentStatus::OVERDUE->value,
   188	                ])
   189	                ->update(['status' => InstallmentStatus::CANCELLED->value]);
   190	
   191	            activity()
   192	                ->performedOn($plan)
   193	                ->causedBy(Auth::user())
   194	                ->withProperties(['reason' => $reason])
   195	                ->log('installment_plan_cancelled');
   196	
   197	            return $plan->fresh(['installments']);
   198	        });
   199	    }
   200	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [358]: app/Services/Financial/InstallmentService.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [359/494]: app/Services/Financial/NumberingService.php
│ LANGUAGE: php | LINES: 63 | SIZE: 1896 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Services\Financial;
     4	
     5	use App\Exceptions\SequenceGenerationException;
     6	use App\Models\NumberingSequence;
     7	use Illuminate\Support\Facades\DB;
     8	
     9	class NumberingService
    10	{
    11	    public function generate(string $sequenceKey): string
    12	    {
    13	        return DB::transaction(function () use ($sequenceKey) {
    14	            $sequence = NumberingSequence::where('sequence_key', $sequenceKey)
    15	                ->where('is_active', true)
    16	                ->lockForUpdate()
    17	                ->first();
    18	
    19	            if (!$sequence) {
    20	                throw new SequenceGenerationException($sequenceKey);
    21	            }
    22	
    23	            $sequence->increment('current_value', $sequence->increment_by);
    24	            $sequence->refresh();
    25	
    26	            $number = (string) $sequence->current_value;
    27	
    28	            if ($sequence->pad_length > 0) {
    29	                $number = str_pad($number, $sequence->pad_length, '0', STR_PAD_LEFT);
    30	            }
    31	
    32	            return $sequence->prefix . $number . $sequence->suffix;
    33	        });
    34	    }
    35	
    36	    public function peek(string $sequenceKey): string
    37	    {
    38	        $sequence = NumberingSequence::where('sequence_key', $sequenceKey)
    39	            ->where('is_active', true)
    40	            ->first();
    41	
    42	        if (!$sequence) {
    43	            return '???';
    44	        }
    45	
    46	        $nextValue = $sequence->current_value + $sequence->increment_by;
    47	        $number = (string) $nextValue;
    48	
    49	        if ($sequence->pad_length > 0) {
    50	            $number = str_pad($number, $sequence->pad_length, '0', STR_PAD_LEFT);
    51	        }
    52	
    53	        return $sequence->prefix . $number . $sequence->suffix;
    54	    }
    55	
    56	    public function resetForFiscalYear(int $fiscalYearId): void
    57	    {
    58	        NumberingSequence::where('reset_on_fiscal_year', true)
    59	            ->update([
    60	                'current_value' => DB::raw('start_value - increment_by'),
    61	                'fiscal_year_id' => $fiscalYearId,
    62	            ]);
    63	    }
    64	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [359]: app/Services/Financial/NumberingService.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [360/494]: app/Services/Financial/ReceiptService.php
│ LANGUAGE: php | LINES: 311 | SIZE: 12757 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Services\Financial;
     4	
     5	use App\Models\Receipt;
     6	use App\Models\ReceiptItem;
     7	use App\Models\Member;
     8	use App\Models\Subscription;
     9	use App\Models\InstallmentPlan;
    10	use App\Models\Installment;
    11	use App\Models\CashRegister;
    12	use App\Enums\ReceiptStatus;
    13	use App\Enums\FeeCategory;
    14	use App\Enums\PaymentStatus;
    15	use Illuminate\Support\Facades\DB;
    16	use Illuminate\Support\Facades\Auth;
    17	
    18	class ReceiptService
    19	{
    20	    public function __construct(
    21	        protected SequenceService $sequenceService,
    22	        protected CashRegisterService $cashRegisterService,
    23	    ) {}
    24	
    25	    /**
    26	     * Create a receipt with its line items.
    27	     */
    28	    public function createReceipt(array $data): Receipt
    29	    {
    30	        return DB::transaction(function () use ($data) {
    31	            // Validate cash register is open
    32	            $cashRegister = $this->cashRegisterService->getOpenRegister();
    33	            if (!$cashRegister) {
    34	                throw new \RuntimeException('لا يوجد صندوق مفتوح. يرجى فتح الصندوق أولاً.');
    35	            }
    36	
    37	            // Determine receipt sequence based on fee category
    38	            $sequenceKey = $this->resolveSequenceKey($data['fee_category'] ?? 'general');
    39	            $receiptNumber = $this->sequenceService->nextNumber($sequenceKey);
    40	
    41	            // Calculate totals from items
    42	            $items = $data['items'] ?? [];
    43	            $subtotal = collect($items)->sum(fn($item) => ($item['amount'] ?? 0) * ($item['quantity'] ?? 1));
    44	            $discount = (float) ($data['discount'] ?? 0);
    45	            $vatRate = (float) ($data['vat_rate'] ?? 0);
    46	            $taxableAmount = $subtotal - $discount;
    47	            $vatAmount = $vatRate > 0 ? round($taxableAmount * ($vatRate / 100), 2) : 0;
    48	            $totalAmount = $taxableAmount + $vatAmount;
    49	            $amountPaid = (float) ($data['amount_paid'] ?? $totalAmount);
    50	
    51	            $receipt = Receipt::create([
    52	                'receipt_number' => $receiptNumber,
    53	                'member_id' => $data['member_id'],
    54	                'subscription_id' => $data['subscription_id'] ?? null,
    55	                'installment_plan_id' => $data['installment_plan_id'] ?? null,
    56	                'installment_id' => $data['installment_id'] ?? null,
    57	                'cash_register_id' => $cashRegister->id,
    58	                'fee_category' => $data['fee_category'] ?? FeeCategory::ANNUAL_SUBSCRIPTION->value,
    59	                'receipt_date' => $data['receipt_date'] ?? now()->toDateString(),
    60	                'subtotal' => $subtotal,
    61	                'discount_amount' => $discount,
    62	                'discount_reason' => $data['discount_reason'] ?? null,
    63	                'board_offer_id' => $data['board_offer_id'] ?? null,
    64	                'vat_rate' => $vatRate,
    65	                'vat_amount' => $vatAmount,
    66	                'total_amount' => $totalAmount,
    67	                'amount_paid' => $amountPaid,
    68	                'amount_remaining' => max(0, $totalAmount - $amountPaid),
    69	                'payment_method' => $data['payment_method'] ?? 'cash',
    70	                'payment_reference' => $data['payment_reference'] ?? null,
    71	                'status' => $amountPaid >= $totalAmount ? ReceiptStatus::PAID->value : ReceiptStatus::PARTIAL->value,
    72	                'notes_ar' => $data['notes_ar'] ?? null,
    73	                'notes_en' => $data['notes_en'] ?? null,
    74	                'created_by' => Auth::id(),
    75	                'fiscal_year' => $data['fiscal_year'] ?? now()->year,
    76	            ]);
    77	
    78	            // Create line items
    79	            foreach ($items as $index => $item) {
    80	                ReceiptItem::create([
    81	                    'receipt_id' => $receipt->id,
    82	                    'fee_category' => $item['category'] ?? $data['fee_category'],
    83	                    'description_ar' => $item['description_ar'] ?? '',
    84	                    'description_en' => $item['description_en'] ?? '',
    85	                    'amount' => $item['amount'] ?? 0,
    86	                    'quantity' => $item['quantity'] ?? 1,
    87	                    'subtotal' => ($item['amount'] ?? 0) * ($item['quantity'] ?? 1),
    88	                    'sort_order' => $index,
    89	                ]);
    90	            }
    91	
    92	            // Update cash register running total
    93	            $this->cashRegisterService->addTransaction($cashRegister, $amountPaid, $receipt);
    94	
    95	            // Update member financial summary
    96	            $this->updateMemberFinancials($receipt);
    97	
    98	            // If linked to subscription, update it
    99	            if ($receipt->subscription_id) {
   100	                $this->updateSubscriptionPayment($receipt);
   101	            }
   102	
   103	            // If linked to installment, update it
   104	            if ($receipt->installment_id) {
   105	                $this->updateInstallmentPayment($receipt);
   106	            }
   107	
   108	            activity()
   109	                ->performedOn($receipt)
   110	                ->causedBy(Auth::user())
   111	                ->withProperties([
   112	                    'receipt_number' => $receiptNumber,
   113	                    'total_amount' => $totalAmount,
   114	                    'amount_paid' => $amountPaid,
   115	                    'member_id' => $data['member_id'],
   116	                ])
   117	                ->log('receipt_created');
   118	
   119	            return $receipt->fresh(['items', 'member']);
   120	        });
   121	    }
   122	
   123	    /**
   124	     * Void a receipt (reverse it).
   125	     */
   126	    public function voidReceipt(Receipt $receipt, string $reason): Receipt
   127	    {
   128	        return DB::transaction(function () use ($receipt, $reason) {
   129	            if ($receipt->status === ReceiptStatus::VOIDED->value) {
   130	                throw new \RuntimeException('هذا الإيصال ملغي بالفعل.');
   131	            }
   132	
   133	            $previousStatus = $receipt->status;
   134	            $previousAmountPaid = $receipt->amount_paid;
   135	
   136	            $receipt->update([
   137	                'status' => ReceiptStatus::VOIDED->value,
   138	                'void_reason' => $reason,
   139	                'voided_by' => Auth::id(),
   140	                'voided_at' => now(),
   141	            ]);
   142	
   143	            // Reverse cash register transaction
   144	            if ($receipt->cash_register_id) {
   145	                $cashRegister = CashRegister::find($receipt->cash_register_id);
   146	                if ($cashRegister) {
   147	                    $this->cashRegisterService->reverseTransaction($cashRegister, $previousAmountPaid, $receipt);
   148	                }
   149	            }
   150	
   151	            // Reverse member financial summary
   152	            $this->reverseMemberFinancials($receipt, $previousAmountPaid);
   153	
   154	            // Reverse subscription payment
   155	            if ($receipt->subscription_id) {
   156	                $this->reverseSubscriptionPayment($receipt, $previousAmountPaid);
   157	            }
   158	
   159	            // Reverse installment payment
   160	            if ($receipt->installment_id) {
   161	                $this->reverseInstallmentPayment($receipt, $previousAmountPaid);
   162	            }
   163	
   164	            activity()
   165	                ->performedOn($receipt)
   166	                ->causedBy(Auth::user())
   167	                ->withProperties([
   168	                    'receipt_number' => $receipt->receipt_number,
   169	                    'voided_amount' => $previousAmountPaid,
   170	                    'reason' => $reason,
   171	                ])
   172	                ->log('receipt_voided');
   173	
   174	            return $receipt->fresh();
   175	        });
   176	    }
   177	
   178	    /**
   179	     * Print receipt (returns data for PDF generation).
   180	     */
   181	    public function getReceiptPrintData(Receipt $receipt): array
   182	    {
   183	        $receipt->load(['member', 'items', 'createdBy', 'subscription']);
   184	
   185	        return [
   186	            'receipt' => $receipt,
   187	            'member' => $receipt->member,
   188	            'items' => $receipt->items->sortBy('sort_order'),
   189	            'club_name_ar' => $this->getSetting('club_name_ar', 'النادي'),
   190	            'club_name_en' => $this->getSetting('club_name_en', 'The Club'),
   191	            'club_address' => $this->getSetting('club_address', ''),
   192	            'club_phone' => $this->getSetting('club_phone', ''),
   193	            'club_logo' => $this->getSetting('club_logo_path', ''),
   194	            'print_date' => now()->format('Y-m-d H:i'),
   195	            'cashier_name' => $receipt->createdBy?->name ?? 'N/A',
   196	        ];
   197	    }
   198	
   199	    /**
   200	     * Determine which sequence key to use based on fee category.
   201	     */
   202	    protected function resolveSequenceKey(string $feeCategory): string
   203	    {
   204	        return match ($feeCategory) {
   205	            FeeCategory::APPLICATION_FORM->value, 'application_form' => 'receipt_app',
   206	            FeeCategory::MEMBERSHIP_VALUE->value, 'membership_value' => 'receipt_mbv',
   207	            FeeCategory::ANNUAL_SUBSCRIPTION->value, 'annual_subscription' => 'receipt_sub',
   208	            FeeCategory::DEPENDENT_FEE->value, FeeCategory::DEPENDENT_SUBSCRIPTION->value, 'dependent_fee', 'dependent_subscription' => 'receipt_dep',
   209	            FeeCategory::CARD_ISSUANCE->value, FeeCategory::CARD_REPLACEMENT->value, 'card_issuance', 'card_replacement' => 'receipt_crd',
   210	            FeeCategory::PENALTY->value, 'penalty' => 'receipt_pen',
   211	            FeeCategory::TRANSFER_FEE->value, 'transfer_fee' => 'receipt_trn',
   212	            default => 'receipt_sub',
   213	        };
   214	    }
   215	
   216	    protected function updateMemberFinancials(Receipt $receipt): void
   217	    {
   218	        $member = Member::find($receipt->member_id);
   219	        if (!$member) return;
   220	
   221	        $member->increment('total_paid', $receipt->amount_paid);
   222	        $member->decrement('total_outstanding', $receipt->amount_paid);
   223	        $member->update(['last_payment_date' => $receipt->receipt_date]);
   224	    }
   225	
   226	    protected function reverseMemberFinancials(Receipt $receipt, float $amount): void
   227	    {
   228	        $member = Member::find($receipt->member_id);
   229	        if (!$member) return;
   230	
   231	        $member->decrement('total_paid', $amount);
   232	        $member->increment('total_outstanding', $amount);
   233	    }
   234	
   235	    protected function updateSubscriptionPayment(Receipt $receipt): void
   236	    {
   237	        $subscription = Subscription::find($receipt->subscription_id);
   238	        if (!$subscription) return;
   239	
   240	        $subscription->increment('amount_paid', $receipt->amount_paid);
   241	        $subscription->amount_remaining = max(0, $subscription->total_amount - $subscription->amount_paid);
   242	
   243	        if ($subscription->amount_remaining <= 0) {
   244	            $subscription->status = SubscriptionStatus::PAID->value;
   245	        } else {
   246	            $subscription->status = SubscriptionStatus::PARTIALLY_PAID->value;
   247	        }
   248	
   249	        $subscription->last_payment_date = $receipt->receipt_date;
   250	        $subscription->save();
   251	    }
   252	
   253	    protected function reverseSubscriptionPayment(Receipt $receipt, float $amount): void
   254	    {
   255	        $subscription = Subscription::find($receipt->subscription_id);
   256	        if (!$subscription) return;
   257	
   258	        $subscription->decrement('amount_paid', $amount);
   259	        $subscription->amount_remaining = max(0, $subscription->total_amount - $subscription->amount_paid);
   260	
   261	        if ($subscription->amount_paid <= 0) {
   262	            $subscription->status = SubscriptionStatus::DUE->value;
   263	        } else {
   264	            $subscription->status = SubscriptionStatus::PARTIALLY_PAID->value;
   265	        }
   266	
   267	        $subscription->save();
   268	    }
   269	
   270	    protected function updateInstallmentPayment(Receipt $receipt): void
   271	    {
   272	        $installment = Installment::find($receipt->installment_id);
   273	        if (!$installment) return;
   274	
   275	        $installment->increment('amount_paid', $receipt->amount_paid);
   276	        $installment->amount_remaining = max(0, $installment->amount - $installment->amount_paid);
   277	        $installment->status = $installment->amount_remaining <= 0 ? 'paid' : 'partially_paid';
   278	        $installment->paid_at = $installment->amount_remaining <= 0 ? now() : null;
   279	        $installment->save();
   280	
   281	        // Check if all installments are paid → mark plan as completed
   282	        $plan = InstallmentPlan::find($receipt->installment_plan_id);
   283	        if ($plan) {
   284	            $unpaidCount = $plan->installments()
   285	                ->where('status', '!=', 'paid')
   286	                ->count();
   287	
   288	            if ($unpaidCount === 0) {
   289	                $plan->update(['status' => 'completed']);
   290	            }
   291	        }
   292	    }
   293	
   294	    protected function reverseInstallmentPayment(Receipt $receipt, float $amount): void
   295	    {
   296	        $installment = Installment::find($receipt->installment_id);
   297	        if (!$installment) return;
   298	
   299	        $installment->decrement('amount_paid', $amount);
   300	        $installment->amount_remaining = max(0, $installment->amount - $installment->amount_paid);
   301	        $installment->status = $installment->amount_paid <= 0 ? 'pending' : 'partially_paid';
   302	        $installment->paid_at = null;
   303	        $installment->save();
   304	    }
   305	
   306	    protected function getSetting(string $key, mixed $default = null): mixed
   307	    {
   308	        return DB::table('system_settings')
   309	            ->where('setting_key', $key)
   310	            ->value('setting_value') ?? $default;
   311	    }
   312	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [360]: app/Services/Financial/ReceiptService.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [361/494]: app/Services/Financial/SequenceService.php
│ LANGUAGE: php | LINES: 92 | SIZE: 3022 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Services\Financial;
     4	
     5	use Illuminate\Support\Facades\DB;
     6	
     7	class SequenceService
     8	{
     9	    /**
    10	     * Generate next number in a sequence. Thread-safe via row-level locking.
    11	     */
    12	    public function nextNumber(string $sequenceKey): string
    13	    {
    14	        return DB::transaction(function () use ($sequenceKey) {
    15	            $sequence = DB::table('sequences')
    16	                ->where('sequence_key', $sequenceKey)
    17	                ->where('is_archived', false)
    18	                ->lockForUpdate()
    19	                ->first();
    20	
    21	            if (!$sequence) {
    22	                throw new \RuntimeException("Sequence not found: {$sequenceKey}");
    23	            }
    24	
    25	            // Check if we need to reset for fiscal year
    26	            if ($sequence->reset_on_fiscal_year) {
    27	                $currentFiscalYear = $this->getCurrentFiscalYear();
    28	                $lastResetYear = $sequence->last_reset_year ?? 0;
    29	
    30	                if ($currentFiscalYear > $lastResetYear) {
    31	                    DB::table('sequences')
    32	                        ->where('id', $sequence->id)
    33	                        ->update([
    34	                            'current_value' => $sequence->start_value,
    35	                            'last_reset_year' => $currentFiscalYear,
    36	                            'updated_at' => now(),
    37	                        ]);
    38	
    39	                    $nextValue = $sequence->start_value;
    40	                } else {
    41	                    $nextValue = $sequence->current_value + $sequence->increment_by;
    42	                }
    43	            } else {
    44	                $nextValue = $sequence->current_value + $sequence->increment_by;
    45	            }
    46	
    47	            DB::table('sequences')
    48	                ->where('id', $sequence->id)
    49	                ->update([
    50	                    'current_value' => $nextValue,
    51	                    'updated_at' => now(),
    52	                ]);
    53	
    54	            $paddedNumber = str_pad((string) $nextValue, $sequence->pad_length, '0', STR_PAD_LEFT);
    55	
    56	            $fiscalSuffix = '';
    57	            if ($sequence->reset_on_fiscal_year) {
    58	                $fiscalSuffix = '-' . $this->getCurrentFiscalYear();
    59	            }
    60	
    61	            return $sequence->prefix . $paddedNumber . $sequence->suffix . $fiscalSuffix;
    62	        });
    63	    }
    64	
    65	    /**
    66	     * Get current value without incrementing.
    67	     */
    68	    public function currentNumber(string $sequenceKey): int
    69	    {
    70	        return (int) DB::table('sequences')
    71	            ->where('sequence_key', $sequenceKey)
    72	            ->where('is_archived', false)
    73	            ->value('current_value');
    74	    }
    75	
    76	    /**
    77	     * Get the current fiscal year based on system settings.
    78	     */
    79	    protected function getCurrentFiscalYear(): int
    80	    {
    81	        $fiscalStartMonth = (int) (DB::table('system_settings')
    82	            ->where('setting_key', 'fiscal_year_start_month')
    83	            ->value('setting_value') ?? 1);
    84	
    85	        $now = now();
    86	
    87	        if ($fiscalStartMonth === 1) {
    88	            return $now->year;
    89	        }
    90	
    91	        return $now->month >= $fiscalStartMonth ? $now->year : $now->year - 1;
    92	    }
    93	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [361]: app/Services/Financial/SequenceService.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [362/494]: app/Services/Financial/SubscriptionService.php
│ LANGUAGE: php | LINES: 264 | SIZE: 10268 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Services\Financial;
     4	
     5	use App\Models\Member;
     6	use App\Models\Subscription;
     7	use App\Enums\SubscriptionStatus;
     8	use App\Enums\MembershipStatus;
     9	use Illuminate\Support\Facades\DB;
    10	use Illuminate\Support\Facades\Auth;
    11	use Illuminate\Support\Collection;
    12	use Carbon\Carbon;
    13	
    14	class SubscriptionService
    15	{
    16	    public function __construct(
    17	        protected FeeCalculationService $feeCalculationService,
    18	        protected ReceiptService $receiptService,
    19	    ) {}
    20	
    21	    /**
    22	     * Generate subscriptions for a given year for all active members.
    23	     */
    24	    public function generateBulkSubscriptions(int $year, ?int $membershipTypeId = null): array
    25	    {
    26	        $query = Member::where('is_archived', false)
    27	            ->whereHas('status', fn($q) => $q->where('status_key', 'active'));
    28	
    29	        if ($membershipTypeId) {
    30	            $query->where('membership_type_id', $membershipTypeId);
    31	        }
    32	
    33	        // Exclude members who already have a subscription for this year
    34	        $query->whereDoesntHave('subscriptions', function ($q) use ($year) {
    35	            $q->where('subscription_year', $year)->where('is_archived', false);
    36	        });
    37	
    38	        $members = $query->get();
    39	        $generated = 0;
    40	        $errors = [];
    41	
    42	        foreach ($members as $member) {
    43	            try {
    44	                $this->createSubscription($member, $year);
    45	                $generated++;
    46	            } catch (\Throwable $e) {
    47	                $errors[] = [
    48	                    'member_id' => $member->id,
    49	                    'membership_number' => $member->membership_number,
    50	                    'error' => $e->getMessage(),
    51	                ];
    52	            }
    53	        }
    54	
    55	        activity()
    56	            ->causedBy(Auth::user())
    57	            ->withProperties([
    58	                'year' => $year,
    59	                'generated_count' => $generated,
    60	                'error_count' => count($errors),
    61	            ])
    62	            ->log('bulk_subscriptions_generated');
    63	
    64	        return [
    65	            'generated' => $generated,
    66	            'errors' => $errors,
    67	            'total_members' => $members->count(),
    68	        ];
    69	    }
    70	
    71	    /**
    72	     * Create a subscription for a single member.
    73	     */
    74	    public function createSubscription(Member $member, int $year): Subscription
    75	    {
    76	        return DB::transaction(function () use ($member, $year) {
    77	            // Check if subscription already exists
    78	            $existing = Subscription::where('member_id', $member->id)
    79	                ->where('subscription_year', $year)
    80	                ->where('is_archived', false)
    81	                ->first();
    82	
    83	            if ($existing) {
    84	                throw new \RuntimeException("الاشتراك موجود بالفعل للعضو {$member->membership_number} لسنة {$year}");
    85	            }
    86	
    87	            $calculation = $this->feeCalculationService->calculateAnnualSubscription($member, $year);
    88	            $dueDateSetting = DB::table('system_settings')
    89	                ->where('setting_key', 'subscription_due_date')
    90	                ->value('setting_value') ?? '01-01';
    91	
    92	            $dueDate = Carbon::createFromFormat('d-m-Y', "{$dueDateSetting}-{$year}");
    93	            $graceDays = (int) (DB::table('system_settings')
    94	                ->where('setting_key', 'grace_period_days')
    95	                ->value('setting_value') ?? 90);
    96	
    97	            $subscription = Subscription::create([
    98	                'member_id' => $member->id,
    99	                'subscription_year' => $year,
   100	                'membership_type_id' => $member->membership_type_id,
   101	                'total_amount' => $calculation['total'],
   102	                'amount_paid' => 0,
   103	                'amount_remaining' => $calculation['total'],
   104	                'due_date' => $dueDate,
   105	                'grace_period_end' => $dueDate->copy()->addDays($graceDays),
   106	                'status' => SubscriptionStatus::DUE->value,
   107	                'fee_breakdown' => json_encode($calculation['fees']),
   108	                'created_by' => Auth::id(),
   109	            ]);
   110	
   111	            // Update member's paid_through_year if needed
   112	            $member->update(['total_outstanding' => DB::raw("total_outstanding + {$calculation['total']}")]);
   113	
   114	            return $subscription;
   115	        });
   116	    }
   117	
   118	    /**
   119	     * Process subscription payment (full or partial).
   120	     */
   121	    public function processPayment(Subscription $subscription, float $amount, array $paymentData = []): array
   122	    {
   123	        return DB::transaction(function () use ($subscription, $amount, $paymentData) {
   124	            if ($amount <= 0) {
   125	                throw new \RuntimeException('مبلغ الدفع يجب أن يكون أكبر من صفر.');
   126	            }
   127	
   128	            if ($amount > $subscription->amount_remaining) {
   129	                throw new \RuntimeException(
   130	                    "المبلغ ({$amount}) أكبر من المتبقي ({$subscription->amount_remaining})."
   131	                );
   132	            }
   133	
   134	            $member = $subscription->member;
   135	
   136	            // Build receipt items from fee breakdown
   137	            $feeBreakdown = json_decode($subscription->fee_breakdown, true) ?? [];
   138	            $items = [];
   139	            foreach ($feeBreakdown as $fee) {
   140	                $items[] = [
   141	                    'category' => $fee['category'],
   142	                    'description_ar' => $fee['description_ar'],
   143	                    'description_en' => $fee['description_en'] ?? '',
   144	                    'amount' => $fee['subtotal'],
   145	                    'quantity' => 1,
   146	                ];
   147	            }
   148	
   149	            // If partial payment, adjust items proportionally
   150	            if ($amount < $subscription->amount_remaining) {
   151	                $ratio = $amount / $subscription->total_amount;
   152	                $items = [[
   153	                    'category' => 'annual_subscription',
   154	                    'description_ar' => "دفعة جزئية - اشتراك سنوي {$subscription->subscription_year}",
   155	                    'description_en' => "Partial Payment - Annual Subscription {$subscription->subscription_year}",
   156	                    'amount' => $amount,
   157	                    'quantity' => 1,
   158	                ]];
   159	            }
   160	
   161	            $receipt = $this->receiptService->createReceipt([
   162	                'member_id' => $member->id,
   163	                'subscription_id' => $subscription->id,
   164	                'installment_plan_id' => $paymentData['installment_plan_id'] ?? null,
   165	                'installment_id' => $paymentData['installment_id'] ?? null,
   166	                'fee_category' => 'annual_subscription',
   167	                'items' => $items,
   168	                'amount_paid' => $amount,
   169	                'payment_method' => $paymentData['payment_method'] ?? 'cash',
   170	                'payment_reference' => $paymentData['payment_reference'] ?? null,
   171	                'notes_ar' => $paymentData['notes_ar'] ?? "سداد اشتراك سنة {$subscription->subscription_year}",
   172	            ]);
   173	
   174	            // Check if fully paid → update paid_through_year
   175	            $subscription->refresh();
   176	            if ($subscription->status === SubscriptionStatus::PAID->value) {
   177	                $member->update(['paid_through_year' => max($member->paid_through_year ?? 0, $subscription->subscription_year)]);
   178	            }
   179	
   180	            return [
   181	                'receipt' => $receipt,
   182	                'subscription' => $subscription->fresh(),
   183	            ];
   184	        });
   185	    }
   186	
   187	    /**
   188	     * Mark overdue subscriptions.
   189	     */
   190	    public function markOverdueSubscriptions(): int
   191	    {
   192	        $count = Subscription::where('status', SubscriptionStatus::DUE->value)
   193	            ->orWhere('status', SubscriptionStatus::PARTIALLY_PAID->value)
   194	            ->where('due_date', '<', now())
   195	            ->where('grace_period_end', '<', now())
   196	            ->where('is_archived', false)
   197	            ->update(['status' => SubscriptionStatus::OVERDUE->value]);
   198	
   199	        if ($count > 0) {
   200	            activity()
   201	                ->causedBy(Auth::user())
   202	                ->withProperties(['count' => $count, 'date' => now()->toDateString()])
   203	                ->log('subscriptions_marked_overdue');
   204	        }
   205	
   206	        return $count;
   207	    }
   208	
   209	    /**
   210	     * Get subscription summary for a member.
   211	     */
   212	    public function getMemberSubscriptionSummary(Member $member): array
   213	    {
   214	        $subscriptions = $member->subscriptions()
   215	            ->where('is_archived', false)
   216	            ->orderByDesc('subscription_year')
   217	            ->get();
   218	
   219	        return [
   220	            'total_subscriptions' => $subscriptions->count(),
   221	            'total_amount' => $subscriptions->sum('total_amount'),
   222	            'total_paid' => $subscriptions->sum('amount_paid'),
   223	            'total_outstanding' => $subscriptions->sum('amount_remaining'),
   224	            'paid_through_year' => $member->paid_through_year,
   225	            'overdue_count' => $subscriptions->where('status', SubscriptionStatus::OVERDUE->value)->count(),
   226	            'current_year_status' => $subscriptions->firstWhere('subscription_year', now()->year)?->status ?? 'none',
   227	            'subscriptions' => $subscriptions,
   228	        ];
   229	    }
   230	
   231	    /**
   232	     * Waive a subscription (board decision).
   233	     */
   234	    public function waiveSubscription(Subscription $subscription, string $reason, ?string $boardDecisionRef = null): Subscription
   235	    {
   236	        return DB::transaction(function () use ($subscription, $reason, $boardDecisionRef) {
   237	            $waived = $subscription->amount_remaining;
   238	
   239	            $subscription->update([
   240	                'status' => SubscriptionStatus::WAIVED->value,
   241	                'amount_remaining' => 0,
   242	                'waive_reason' => $reason,
   243	                'board_decision_reference' => $boardDecisionRef,
   244	                'waived_by' => Auth::id(),
   245	                'waived_at' => now(),
   246	            ]);
   247	
   248	            // Update member outstanding
   249	            $member = $subscription->member;
   250	            $member->decrement('total_outstanding', $waived);
   251	
   252	            activity()
   253	                ->performedOn($subscription)
   254	                ->causedBy(Auth::user())
   255	                ->withProperties([
   256	                    'waived_amount' => $waived,
   257	                    'reason' => $reason,
   258	                    'board_ref' => $boardDecisionRef,
   259	                ])
   260	                ->log('subscription_waived');
   261	
   262	            return $subscription->fresh();
   263	        });
   264	    }
   265	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [362]: app/Services/Financial/SubscriptionService.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [363/494]: app/Services/Membership/DependentService.php
│ LANGUAGE: php | LINES: 419 | SIZE: 14807 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	declare(strict_types=1);
     4	
     5	namespace App\Services\Membership;
     6	
     7	use App\Enums\DependentStatus;
     8	use App\Enums\AuditAction;
     9	use App\Enums\CardStatus;
    10	use App\Models\Dependent;
    11	use App\Models\Member;
    12	use App\Services\SequenceService;
    13	use App\Services\SettingsService;
    14	use Illuminate\Support\Facades\DB;
    15	use Illuminate\Support\Facades\Auth;
    16	use Spatie\Activitylog\Facades\Activity;
    17	use Illuminate\Support\Collection;
    18	
    19	final class DependentService
    20	{
    21	    public function __construct(
    22	        private readonly SequenceService $sequenceService,
    23	        private readonly SettingsService $settingsService,
    24	    ) {}
    25	
    26	    /**
    27	     * Create a new dependent for a member.
    28	     */
    29	    public function createDependent(Member $member, array $data): Dependent
    30	    {
    31	        return DB::transaction(function () use ($member, $data) {
    32	            $this->validateDependentLimits($member);
    33	            $this->validateAgeRestrictions($data);
    34	            $this->validateNationalIdUniqueness($data['national_id'] ?? null, $member->id);
    35	
    36	            $data['member_id'] = $member->id;
    37	            $data['status'] = DependentStatus::PENDING_REVIEW;
    38	            $data['created_by'] = Auth::id();
    39	
    40	            $dependent = Dependent::create($data);
    41	
    42	            activity('dependent')
    43	                ->performedOn($dependent)
    44	                ->causedBy(Auth::user())
    45	                ->withProperties([
    46	                    'action' => AuditAction::CREATED->value,
    47	                    'member_id' => $member->id,
    48	                    'member_name' => $member->full_name_ar,
    49	                    'dependent_name' => $dependent->full_name_ar,
    50	                ])
    51	                ->log("تم إضافة تابع جديد: {$dependent->full_name_ar} للعضو: {$member->full_name_ar}");
    52	
    53	            return $dependent;
    54	        });
    55	    }
    56	
    57	    /**
    58	     * Update dependent information.
    59	     */
    60	    public function updateDependent(Dependent $dependent, array $data): Dependent
    61	    {
    62	        return DB::transaction(function () use ($dependent, $data) {
    63	            if (isset($data['national_id'])) {
    64	                $this->validateNationalIdUniqueness($data['national_id'], $dependent->member_id, $dependent->id);
    65	            }
    66	
    67	            $oldData = $dependent->only(['full_name_ar', 'full_name_en', 'phone', 'status']);
    68	            $data['updated_by'] = Auth::id();
    69	
    70	            $dependent->update($data);
    71	
    72	            activity('dependent')
    73	                ->performedOn($dependent)
    74	                ->causedBy(Auth::user())
    75	                ->withProperties([
    76	                    'action' => AuditAction::UPDATED->value,
    77	                    'old' => $oldData,
    78	                    'new' => $dependent->only(['full_name_ar', 'full_name_en', 'phone', 'status']),
    79	                ])
    80	                ->log("تم تعديل بيانات التابع: {$dependent->full_name_ar}");
    81	
    82	            return $dependent->fresh();
    83	        });
    84	    }
    85	
    86	    /**
    87	     * Approve a pending dependent.
    88	     */
    89	    public function approveDependent(Dependent $dependent, ?string $notes = null): Dependent
    90	    {
    91	        $this->assertStatus($dependent, [DependentStatus::PENDING_REVIEW, DependentStatus::PENDING_DOCUMENTS]);
    92	
    93	        return DB::transaction(function () use ($dependent, $notes) {
    94	            $dependent->update([
    95	                'status' => DependentStatus::ACTIVE,
    96	                'approved_by' => Auth::id(),
    97	                'approved_at' => now(),
    98	                'review_notes' => $notes,
    99	                'updated_by' => Auth::id(),
   100	            ]);
   101	
   102	            activity('dependent')
   103	                ->performedOn($dependent)
   104	                ->causedBy(Auth::user())
   105	                ->withProperties([
   106	                    'action' => AuditAction::STATUS_CHANGED->value,
   107	                    'from_status' => DependentStatus::PENDING_REVIEW->value,
   108	                    'to_status' => DependentStatus::ACTIVE->value,
   109	                ])
   110	                ->log("تم الموافقة على التابع: {$dependent->full_name_ar}");
   111	
   112	            return $dependent->fresh();
   113	        });
   114	    }
   115	
   116	    /**
   117	     * Reject a pending dependent.
   118	     */
   119	    public function rejectDependent(Dependent $dependent, string $reason): Dependent
   120	    {
   121	        $this->assertStatus($dependent, [DependentStatus::PENDING_REVIEW, DependentStatus::PENDING_DOCUMENTS]);
   122	
   123	        return DB::transaction(function () use ($dependent, $reason) {
   124	            $dependent->update([
   125	                'status' => DependentStatus::REJECTED,
   126	                'rejection_reason' => $reason,
   127	                'approved_by' => Auth::id(),
   128	                'approved_at' => now(),
   129	                'updated_by' => Auth::id(),
   130	            ]);
   131	
   132	            activity('dependent')
   133	                ->performedOn($dependent)
   134	                ->causedBy(Auth::user())
   135	                ->withProperties([
   136	                    'action' => AuditAction::STATUS_CHANGED->value,
   137	                    'to_status' => DependentStatus::REJECTED->value,
   138	                    'reason' => $reason,
   139	                ])
   140	                ->log("تم رفض التابع: {$dependent->full_name_ar} — السبب: {$reason}");
   141	
   142	            return $dependent->fresh();
   143	        });
   144	    }
   145	
   146	    /**
   147	     * Suspend an active dependent.
   148	     */
   149	    public function suspendDependent(Dependent $dependent, string $reason): Dependent
   150	    {
   151	        $this->assertStatus($dependent, [DependentStatus::ACTIVE]);
   152	
   153	        return DB::transaction(function () use ($dependent, $reason) {
   154	            $dependent->update([
   155	                'status' => DependentStatus::SUSPENDED,
   156	                'suspension_reason' => $reason,
   157	                'suspended_at' => now(),
   158	                'updated_by' => Auth::id(),
   159	            ]);
   160	
   161	            // Suspend active cards too
   162	            $dependent->cards()
   163	                ->where('status', CardStatus::ACTIVE->value)
   164	                ->update(['status' => CardStatus::SUSPENDED->value]);
   165	
   166	            activity('dependent')
   167	                ->performedOn($dependent)
   168	                ->causedBy(Auth::user())
   169	                ->withProperties([
   170	                    'action' => AuditAction::STATUS_CHANGED->value,
   171	                    'to_status' => DependentStatus::SUSPENDED->value,
   172	                    'reason' => $reason,
   173	                ])
   174	                ->log("تم إيقاف التابع: {$dependent->full_name_ar} — السبب: {$reason}");
   175	
   176	            return $dependent->fresh();
   177	        });
   178	    }
   179	
   180	    /**
   181	     * Reactivate a suspended dependent.
   182	     */
   183	    public function reactivateDependent(Dependent $dependent): Dependent
   184	    {
   185	        $this->assertStatus($dependent, [DependentStatus::SUSPENDED]);
   186	
   187	        return DB::transaction(function () use ($dependent) {
   188	            $dependent->update([
   189	                'status' => DependentStatus::ACTIVE,
   190	                'suspension_reason' => null,
   191	                'suspended_at' => null,
   192	                'updated_by' => Auth::id(),
   193	            ]);
   194	
   195	            activity('dependent')
   196	                ->performedOn($dependent)
   197	                ->causedBy(Auth::user())
   198	                ->withProperties([
   199	                    'action' => AuditAction::STATUS_CHANGED->value,
   200	                    'to_status' => DependentStatus::ACTIVE->value,
   201	                ])
   202	                ->log("تم إعادة تفعيل التابع: {$dependent->full_name_ar}");
   203	
   204	            return $dependent->fresh();
   205	        });
   206	    }
   207	
   208	    /**
   209	     * Archive a dependent (soft remove).
   210	     */
   211	    public function archiveDependent(Dependent $dependent, string $reason): Dependent
   212	    {
   213	        return DB::transaction(function () use ($dependent, $reason) {
   214	            $dependent->update([
   215	                'status' => DependentStatus::ARCHIVED,
   216	                'is_archived' => true,
   217	                'archive_reason' => $reason,
   218	                'archived_at' => now(),
   219	                'updated_by' => Auth::id(),
   220	            ]);
   221	
   222	            // Cancel active cards
   223	            $dependent->cards()
   224	                ->whereIn('status', [CardStatus::ACTIVE->value, CardStatus::SUSPENDED->value])
   225	                ->update(['status' => CardStatus::CANCELLED->value]);
   226	
   227	            activity('dependent')
   228	                ->performedOn($dependent)
   229	                ->causedBy(Auth::user())
   230	                ->withProperties([
   231	                    'action' => AuditAction::ARCHIVED->value,
   232	                    'reason' => $reason,
   233	                ])
   234	                ->log("تم أرشفة التابع: {$dependent->full_name_ar} — السبب: {$reason}");
   235	
   236	            return $dependent->fresh();
   237	        });
   238	    }
   239	
   240	    /**
   241	     * Request documents from a dependent.
   242	     */
   243	    public function requestDocuments(Dependent $dependent, string $notes): Dependent
   244	    {
   245	        $this->assertStatus($dependent, [DependentStatus::PENDING_REVIEW]);
   246	
   247	        return DB::transaction(function () use ($dependent, $notes) {
   248	            $dependent->update([
   249	                'status' => DependentStatus::PENDING_DOCUMENTS,
   250	                'review_notes' => $notes,
   251	                'updated_by' => Auth::id(),
   252	            ]);
   253	
   254	            activity('dependent')
   255	                ->performedOn($dependent)
   256	                ->causedBy(Auth::user())
   257	                ->withProperties([
   258	                    'action' => AuditAction::STATUS_CHANGED->value,
   259	                    'to_status' => DependentStatus::PENDING_DOCUMENTS->value,
   260	                    'notes' => $notes,
   261	                ])
   262	                ->log("تم طلب مستندات إضافية للتابع: {$dependent->full_name_ar}");
   263	
   264	            return $dependent->fresh();
   265	        });
   266	    }
   267	
   268	    /**
   269	     * Get dependents count for a member by status.
   270	     */
   271	    public function getDependentCountByStatus(Member $member): Collection
   272	    {
   273	        return $member->dependents()
   274	            ->selectRaw('status, COUNT(*) as count')
   275	            ->groupBy('status')
   276	            ->pluck('count', 'status');
   277	    }
   278	
   279	    /**
   280	     * Get all active dependents for a member.
   281	     */
   282	    public function getActiveDependents(Member $member): Collection
   283	    {
   284	        return $member->dependents()
   285	            ->where('status', DependentStatus::ACTIVE)
   286	            ->orderBy('full_name_ar')
   287	            ->get();
   288	    }
   289	
   290	    /**
   291	     * Calculate the age of a dependent.
   292	     */
   293	    public function calculateAge(Dependent $dependent): ?int
   294	    {
   295	        if (!$dependent->date_of_birth) {
   296	            return null;
   297	        }
   298	
   299	        return $dependent->date_of_birth->age;
   300	    }
   301	
   302	    /**
   303	     * Check if the member can add more dependents.
   304	     */
   305	    public function canAddDependent(Member $member): bool
   306	    {
   307	        $maxDependents = $this->getMaxDependentsForMember($member);
   308	        $currentCount = $member->dependents()
   309	            ->whereNotIn('status', [DependentStatus::REJECTED->value, DependentStatus::ARCHIVED->value])
   310	            ->count();
   311	
   312	        return $currentCount < $maxDependents;
   313	    }
   314	
   315	    /**
   316	     * Get the remaining dependent slots for a member.
   317	     */
   318	    public function getRemainingSlots(Member $member): int
   319	    {
   320	        $maxDependents = $this->getMaxDependentsForMember($member);
   321	        $currentCount = $member->dependents()
   322	            ->whereNotIn('status', [DependentStatus::REJECTED->value, DependentStatus::ARCHIVED->value])
   323	            ->count();
   324	
   325	        return max(0, $maxDependents - $currentCount);
   326	    }
   327	
   328	    /**
   329	     * Bulk approve dependents.
   330	     */
   331	    public function bulkApprove(Collection $dependents): int
   332	    {
   333	        $approved = 0;
   334	
   335	        DB::transaction(function () use ($dependents, &$approved) {
   336	            foreach ($dependents as $dependent) {
   337	                if (in_array($dependent->status, [DependentStatus::PENDING_REVIEW, DependentStatus::PENDING_DOCUMENTS])) {
   338	                    $this->approveDependent($dependent);
   339	                    $approved++;
   340	                }
   341	            }
   342	        });
   343	
   344	        return $approved;
   345	    }
   346	
   347	    // ─── Private Helpers ─────────────────────────────────────────
   348	
   349	    private function validateDependentLimits(Member $member): void
   350	    {
   351	        if (!$this->canAddDependent($member)) {
   352	            $max = $this->getMaxDependentsForMember($member);
   353	            throw new \App\Exceptions\DependentLimitExceededException(
   354	                "لقد تجاوزت الحد الأقصى لعدد التابعين ({$max}) لهذا النوع من العضوية."
   355	            );
   356	        }
   357	    }
   358	
   359	    private function validateAgeRestrictions(array $data): void
   360	    {
   361	        if (!isset($data['date_of_birth']) || !isset($data['relationship'])) {
   362	            return;
   363	        }
   364	
   365	        $dob = \Carbon\Carbon::parse($data['date_of_birth']);
   366	        $age = $dob->age;
   367	        $relationship = $data['relationship'];
   368	
   369	        $maxChildAge = (int) $this->settingsService->get('dependent.max_child_age', 21);
   370	
   371	        if ($relationship === 'child' && $age > $maxChildAge) {
   372	            throw new \App\Exceptions\DependentAgeRestrictionException(
   373	                "عمر الابن/الابنة يتجاوز الحد الأقصى المسموح ({$maxChildAge} سنة)."
   374	            );
   375	        }
   376	    }
   377	
   378	    private function validateNationalIdUniqueness(?string $nationalId, int $memberId, ?int $excludeId = null): void
   379	    {
   380	        if (!$nationalId) {
   381	            return;
   382	        }
   383	
   384	        $query = Dependent::where('national_id', $nationalId)
   385	            ->where('member_id', $memberId)
   386	            ->whereNotIn('status', [DependentStatus::REJECTED->value, DependentStatus::ARCHIVED->value]);
   387	
   388	        if ($excludeId) {
   389	            $query->where('id', '!=', $excludeId);
   390	        }
   391	
   392	        if ($query->exists()) {
   393	            throw new \App\Exceptions\DuplicateNationalIdException(
   394	                "رقم الهوية مسجل بالفعل لتابع آخر لهذا العضو."
   395	            );
   396	        }
   397	    }
   398	
   399	    private function getMaxDependentsForMember(Member $member): int
   400	    {
   401	        // Check membership type setting first, fall back to global setting
   402	        $membershipType = $member->membershipType;
   403	
   404	        if ($membershipType && $membershipType->max_dependents !== null) {
   405	            return $membershipType->max_dependents;
   406	        }
   407	
   408	        return (int) $this->settingsService->get('dependent.max_dependents_per_member', 10);
   409	    }
   410	
   411	    private function assertStatus(Dependent $dependent, array $allowedStatuses): void
   412	    {
   413	        if (!in_array($dependent->status, $allowedStatuses)) {
   414	            $allowed = implode(', ', array_map(fn($s) => $s->getLabel(), $allowedStatuses));
   415	            throw new \App\Exceptions\InvalidStatusTransitionException(
   416	                "لا يمكن تنفيذ هذا الإجراء. الحالة الحالية: {$dependent->status->getLabel()}. الحالات المسموحة: {$allowed}"
   417	            );
   418	        }
   419	    }
   420	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [363]: app/Services/Membership/DependentService.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [364/494]: app/Services/Membership/MemberNumberService.php
│ LANGUAGE: php | LINES: 74 | SIZE: 2546 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Services\Membership;
     4	
     5	use App\Models\SystemSequence;
     6	use Illuminate\Support\Facades\DB;
     7	
     8	class MemberNumberService
     9	{
    10	    /**
    11	     * Generate the next membership number.
    12	     * CRITICAL: Based on PAYMENT order, not application order.
    13	     */
    14	    public function generateMembershipNumber(): string
    15	    {
    16	        return DB::transaction(function () {
    17	            $sequence = SystemSequence::where('sequence_key', 'membership_number')
    18	                ->lockForUpdate()
    19	                ->first();
    20	
    21	            if (!$sequence) {
    22	                throw new \RuntimeException('تسلسل رقم العضوية غير موجود في النظام — تحقق من جدول system_sequences');
    23	            }
    24	
    25	            $nextValue = $sequence->current_value + $sequence->increment_by;
    26	
    27	            $sequence->update(['current_value' => $nextValue]);
    28	
    29	            $paddedValue = str_pad($nextValue, $sequence->pad_length, '0', STR_PAD_LEFT);
    30	
    31	            return $sequence->prefix . $paddedValue . $sequence->suffix;
    32	        });
    33	    }
    34	
    35	    /**
    36	     * Generate a sequence number for any sequence key.
    37	     */
    38	    public function generateSequenceNumber(string $sequenceKey): string
    39	    {
    40	        return DB::transaction(function () use ($sequenceKey) {
    41	            $sequence = SystemSequence::where('sequence_key', $sequenceKey)
    42	                ->lockForUpdate()
    43	                ->first();
    44	
    45	            if (!$sequence) {
    46	                throw new \RuntimeException("التسلسل '{$sequenceKey}' غير موجود في النظام");
    47	            }
    48	
    49	            // Check fiscal year reset
    50	            if ($sequence->reset_on_fiscal_year) {
    51	                $currentYear = now()->year;
    52	                $lastReset = $sequence->last_reset_year;
    53	
    54	                if ($lastReset !== $currentYear) {
    55	                    $sequence->current_value = $sequence->start_value - $sequence->increment_by;
    56	                    $sequence->last_reset_year = $currentYear;
    57	                }
    58	            }
    59	
    60	            $nextValue = $sequence->current_value + $sequence->increment_by;
    61	            $sequence->current_value = $nextValue;
    62	            $sequence->save();
    63	
    64	            $paddedValue = str_pad($nextValue, $sequence->pad_length, '0', STR_PAD_LEFT);
    65	
    66	            // Include fiscal year in prefix if applicable
    67	            $prefix = $sequence->prefix;
    68	            if ($sequence->reset_on_fiscal_year) {
    69	                $prefix .= now()->format('Y') . '-';
    70	            }
    71	
    72	            return $prefix . $paddedValue . $sequence->suffix;
    73	        });
    74	    }
    75	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [364]: app/Services/Membership/MemberNumberService.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [365/494]: app/Services/Membership/MembershipService.php
│ LANGUAGE: php | LINES: 182 | SIZE: 6624 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Services\Membership;
     4	
     5	use App\DTOs\MemberData;
     6	use App\Exceptions\BusinessRuleException;
     7	use App\Exceptions\InvalidStatusTransitionException;
     8	use App\Models\Member;
     9	use App\Models\MembershipStatus;
    10	use App\Models\NumberingSequence;
    11	use App\Models\WorkflowProgress;
    12	use App\Models\WorkflowStage;
    13	use App\Enums\WorkflowProgressStatus;
    14	use Illuminate\Support\Facades\DB;
    15	
    16	class MembershipService
    17	{
    18	    public function createMember(MemberData $data): Member
    19	    {
    20	        return DB::transaction(function () use ($data) {
    21	            $initialStatus = MembershipStatus::where('status_key', 'applicant')->firstOrFail();
    22	
    23	            $member = Member::create(array_merge($data->toArray(), [
    24	                'status_id' => $initialStatus->id,
    25	                'application_date' => now(),
    26	                'created_by' => auth()->id(),
    27	            ]));
    28	
    29	            $this->initializeWorkflow($member);
    30	
    31	            activity()
    32	                ->performedOn($member)
    33	                ->causedBy(auth()->user())
    34	                ->withProperties(['status' => 'applicant'])
    35	                ->log('تم إنشاء طلب عضوية جديد');
    36	
    37	            return $member;
    38	        });
    39	    }
    40	
    41	    public function assignMembershipNumber(Member $member): string
    42	    {
    43	        return DB::transaction(function () use ($member) {
    44	            $number = Member::generateNumber('membership_number');
    45	
    46	            $member->update([
    47	                'membership_number' => $number,
    48	                'updated_by' => auth()->id(),
    49	            ]);
    50	
    51	            return $number;
    52	        });
    53	    }
    54	
    55	    public function transitionStatus(Member $member, string $newStatusKey, ?string $notes = null): Member
    56	    {
    57	        return DB::transaction(function () use ($member, $newStatusKey, $notes) {
    58	            $currentStatus = $member->status;
    59	            $newStatus = MembershipStatus::where('status_key', $newStatusKey)->firstOrFail();
    60	
    61	            if (!$this->isValidTransition($currentStatus->status_key, $newStatusKey)) {
    62	                throw new InvalidStatusTransitionException(
    63	                    $currentStatus->status_key,
    64	                    $newStatusKey,
    65	                    'عضوية'
    66	                );
    67	            }
    68	
    69	            $oldStatusKey = $currentStatus->status_key;
    70	
    71	            $member->update([
    72	                'status_id' => $newStatus->id,
    73	                'updated_by' => auth()->id(),
    74	            ]);
    75	
    76	            if ($newStatusKey === 'active' && !$member->activation_date) {
    77	                $member->update([
    78	                    'activation_date' => now(),
    79	                    'membership_start_date' => now(),
    80	                ]);
    81	            }
    82	
    83	            activity()
    84	                ->performedOn($member)
    85	                ->causedBy(auth()->user())
    86	                ->withProperties([
    87	                    'old_status' => $oldStatusKey,
    88	                    'new_status' => $newStatusKey,
    89	                    'notes' => $notes,
    90	                ])
    91	                ->log("تم تغيير حالة العضوية من {$currentStatus->name_ar} إلى {$newStatus->name_ar}");
    92	
    93	            return $member->fresh();
    94	        });
    95	    }
    96	
    97	    public function activateMember(Member $member): Member
    98	    {
    99	        if (!$member->membership_number) {
   100	            $this->assignMembershipNumber($member);
   101	        }
   102	
   103	        return $this->transitionStatus($member, 'active', 'تم تفعيل العضوية');
   104	    }
   105	
   106	    protected function initializeWorkflow(Member $member): void
   107	    {
   108	        $stages = WorkflowStage::orderBy('stage_order')->get();
   109	
   110	        foreach ($stages as $stage) {
   111	            WorkflowProgress::create([
   112	                'member_id' => $member->id,
   113	                'workflow_stage_id' => $stage->id,
   114	                'status' => $stage->stage_order === 1
   115	                    ? WorkflowProgressStatus::InProgress
   116	                    : WorkflowProgressStatus::Pending,
   117	                'started_at' => $stage->stage_order === 1 ? now() : null,
   118	            ]);
   119	        }
   120	    }
   121	
   122	    public function advanceWorkflow(Member $member, string $stageKey): void
   123	    {
   124	        DB::transaction(function () use ($member, $stageKey) {
   125	            $stage = WorkflowStage::where('stage_key', $stageKey)->firstOrFail();
   126	
   127	            $progress = WorkflowProgress::where('member_id', $member->id)
   128	                ->where('workflow_stage_id', $stage->id)
   129	                ->firstOrFail();
   130	
   131	            $progress->update([
   132	                'status' => WorkflowProgressStatus::Completed,
   133	                'completed_at' => now(),
   134	                'completed_by' => auth()->id(),
   135	            ]);
   136	
   137	            if ($stage->next_stage_order) {
   138	                $nextStage = WorkflowStage::where('stage_order', $stage->next_stage_order)->first();
   139	                if ($nextStage) {
   140	                    WorkflowProgress::where('member_id', $member->id)
   141	                        ->where('workflow_stage_id', $nextStage->id)
   142	                        ->update([
   143	                            'status' => WorkflowProgressStatus::InProgress,
   144	                            'started_at' => now(),
   145	                        ]);
   146	                }
   147	            }
   148	        });
   149	    }
   150	
   151	    public function isValidTransition(string $fromKey, string $toKey): bool
   152	    {
   153	        $transitions = [
   154	            'applicant' => ['pending_documents', 'rejected', 'withdrawn'],
   155	            'pending_documents' => ['pending_interview', 'pending_board_review', 'rejected', 'withdrawn'],
   156	            'pending_interview' => ['pending_board_review', 'rejected', 'withdrawn'],
   157	            'pending_board_review' => ['pending_payment', 'rejected', 'withdrawn'],
   158	            'pending_payment' => ['active', 'withdrawn'],
   159	            'active' => ['suspended', 'frozen', 'expelled', 'withdrawn', 'deceased', 'lapsed', 'transferred_out', 'honorary'],
   160	            'suspended' => ['active', 'expelled', 'withdrawn'],
   161	            'frozen' => ['active', 'withdrawn', 'lapsed'],
   162	            'lapsed' => ['active', 'withdrawn'],
   163	            'honorary' => ['active', 'withdrawn', 'deceased'],
   164	        ];
   165	
   166	        return in_array($toKey, $transitions[$fromKey] ?? []);
   167	    }
   168	
   169	    public function getMemberWorkflowProgress(Member $member): array
   170	    {
   171	        return WorkflowProgress::where('member_id', $member->id)
   172	            ->with('workflowStage')
   173	            ->orderBy('workflow_stage_id')
   174	            ->get()
   175	            ->map(fn ($wp) => [
   176	                'stage' => $wp->workflowStage->name_ar,
   177	                'status' => $wp->status,
   178	                'started_at' => $wp->started_at,
   179	                'completed_at' => $wp->completed_at,
   180	            ])
   181	            ->toArray();
   182	    }
   183	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [365]: app/Services/Membership/MembershipService.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [366/494]: app/Services/Membership/MembershipWorkflowService.php
│ LANGUAGE: php | LINES: 509 | SIZE: 18534 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Services\Membership;
     4	
     5	use App\Enums\BoardDecisionResult;
     6	use App\Enums\InterviewResult;
     7	use App\Enums\InterviewStatus;
     8	use App\Enums\MembershipStatus;
     9	use App\Enums\SubscriptionStatus;
    10	use App\Enums\WorkflowProgressStatus;
    11	use App\Models\Member;
    12	use App\Models\WorkflowProgress;
    13	use Illuminate\Support\Facades\DB;
    14	use Illuminate\Validation\ValidationException;
    15	
    16	class MembershipWorkflowService
    17	{
    18	    public function __construct(
    19	        private MemberNumberService $numberService,
    20	    ) {}
    21	
    22	    /**
    23	     * Initialize all 8 workflow progress records for a new member.
    24	     */
    25	    public function initializeWorkflow(Member $member): void
    26	    {
    27	        $stages = [
    28	            1 => 'تقديم الاستمارة',
    29	            2 => 'تجميع المستندات',
    30	            3 => 'سداد رسوم الاستمارة',
    31	            4 => 'مراجعة الطلب',
    32	            5 => 'المقابلة الشخصية',
    33	            6 => 'قرار مجلس الإدارة',
    34	            7 => 'سداد رسوم العضوية',
    35	            8 => 'إصدار الكارنيهات',
    36	        ];
    37	
    38	        foreach ($stages as $num => $name) {
    39	            WorkflowProgress::create([
    40	                'member_id' => $member->id,
    41	                'stage_number' => $num,
    42	                'stage_name' => $name,
    43	                'status' => $num === 1 ? WorkflowProgressStatus::IN_PROGRESS : WorkflowProgressStatus::PENDING,
    44	            ]);
    45	        }
    46	
    47	        // Stage 1 is immediately in progress upon creation
    48	        $member->update([
    49	            'workflow_stage' => 1,
    50	            'workflow_status' => WorkflowProgressStatus::IN_PROGRESS,
    51	        ]);
    52	    }
    53	
    54	    /**
    55	     * Advance the member to the next workflow stage.
    56	     */
    57	    public function advanceStage(Member $member, ?string $notes = null): void
    58	    {
    59	        $currentStage = $member->workflow_stage;
    60	
    61	        if ($currentStage >= 8) {
    62	            throw new \RuntimeException('العضو أكمل جميع المراحل بالفعل');
    63	        }
    64	
    65	        $this->validateStageCompletion($member, $currentStage);
    66	
    67	        DB::transaction(function () use ($member, $currentStage, $notes) {
    68	            // Complete current stage
    69	            WorkflowProgress::where('member_id', $member->id)
    70	                ->where('stage_number', $currentStage)
    71	                ->update([
    72	                    'status' => WorkflowProgressStatus::COMPLETED,
    73	                    'completed_at' => now(),
    74	                    'completed_by' => auth()->id(),
    75	                    'notes' => $notes,
    76	                ]);
    77	
    78	            $nextStage = $currentStage + 1;
    79	
    80	            // Start next stage
    81	            WorkflowProgress::where('member_id', $member->id)
    82	                ->where('stage_number', $nextStage)
    83	                ->update([
    84	                    'status' => WorkflowProgressStatus::IN_PROGRESS,
    85	                ]);
    86	
    87	            $member->update([
    88	                'workflow_stage' => $nextStage,
    89	                'workflow_status' => WorkflowProgressStatus::IN_PROGRESS,
    90	            ]);
    91	
    92	            activity()
    93	                ->performedOn($member)
    94	                ->causedBy(auth()->user())
    95	                ->withProperties([
    96	                    'from_stage' => $currentStage,
    97	                    'to_stage' => $nextStage,
    98	                    'notes' => $notes,
    99	                ])
   100	                ->log("تم الانتقال من المرحلة {$currentStage} إلى المرحلة {$nextStage}");
   101	        });
   102	    }
   103	
   104	    /**
   105	     * Validate that the current stage requirements are met before advancing.
   106	     */
   107	    private function validateStageCompletion(Member $member, int $stage): void
   108	    {
   109	        match ($stage) {
   110	            1 => $this->validateStage1($member),
   111	            2 => $this->validateStage2($member),
   112	            3 => $this->validateStage3($member),
   113	            4 => $this->validateStage4($member),
   114	            5 => $this->validateStage5($member),
   115	            6 => $this->validateStage6($member),
   116	            7 => $this->validateStage7($member),
   117	            default => null,
   118	        };
   119	    }
   120	
   121	    private function validateStage1(Member $member): void
   122	    {
   123	        $required = ['full_name_ar', 'national_id', 'gender', 'date_of_birth', 'mobile', 'membership_type_id'];
   124	
   125	        foreach ($required as $field) {
   126	            if (empty($member->$field)) {
   127	                throw new \RuntimeException("الحقل المطلوب '{$field}' فارغ — يجب إكمال بيانات الاستمارة");
   128	            }
   129	        }
   130	    }
   131	
   132	    private function validateStage2(Member $member): void
   133	    {
   134	        // Check required documents are uploaded and verified
   135	        $requiredDocs = $member->membershipType
   136	            ?->requiredDocumentTypes()
   137	            ->count() ?? 0;
   138	
   139	        if ($requiredDocs > 0) {
   140	            $uploadedDocs = $member->documents()
   141	                ->whereHas('documentType', fn($q) => $q->where('is_required', true))
   142	                ->where('is_verified', true)
   143	                ->count();
   144	
   145	            if ($uploadedDocs < $requiredDocs) {
   146	                $missing = $requiredDocs - $uploadedDocs;
   147	                throw new \RuntimeException("يوجد {$missing} مستند مطلوب لم يتم رفعه أو التحقق منه");
   148	            }
   149	        }
   150	    }
   151	
   152	    private function validateStage3(Member $member): void
   153	    {
   154	        // Check application fee receipt exists and is paid
   155	        $hasPaidApplicationFee = $member->receipts()
   156	            ->where('fee_category', 'application_form')
   157	            ->where('status', 'paid')
   158	            ->exists();
   159	
   160	        if (!$hasPaidApplicationFee) {
   161	            throw new \RuntimeException('لم يتم سداد رسوم الاستمارة');
   162	        }
   163	    }
   164	
   165	    private function validateStage4(Member $member): void
   166	    {
   167	        // Application review — just needs admin confirmation (no automated check)
   168	        // The advance action itself is the confirmation
   169	    }
   170	
   171	    private function validateStage5(Member $member): void
   172	    {
   173	        // Check interview record exists with a result
   174	        $hasInterview = $member->interviews()
   175	            ->where('status', InterviewStatus::COMPLETED)
   176	            ->whereNotNull('result')
   177	            ->exists();
   178	
   179	        if (!$hasInterview) {
   180	            throw new \RuntimeException('لم يتم إجراء المقابلة الشخصية أو لم تُسجل نتيجتها');
   181	        }
   182	    }
   183	
   184	    private function validateStage6(Member $member): void
   185	    {
   186	        // Check board decision exists and is approved
   187	        $hasBoardApproval = $member->boardDecisions()
   188	            ->where('result', BoardDecisionResult::APPROVED)
   189	            ->exists();
   190	
   191	        if (!$hasBoardApproval) {
   192	            throw new \RuntimeException('لم يتم اتخاذ قرار الموافقة من مجلس الإدارة');
   193	        }
   194	    }
   195	
   196	    private function validateStage7(Member $member): void
   197	    {
   198	        // Check all membership fees are paid
   199	        $hasMembershipPayment = $member->receipts()
   200	            ->where('fee_category', 'membership_value')
   201	            ->where('status', 'paid')
   202	            ->exists();
   203	
   204	        if (!$hasMembershipPayment) {
   205	            throw new \RuntimeException('لم يتم سداد رسوم العضوية');
   206	        }
   207	
   208	        // Check 15-day deadline
   209	        if ($member->approval_date) {
   210	            $deadline = $member->approval_date->addDays(15);
   211	            if (now()->greaterThan($deadline)) {
   212	                throw new \RuntimeException('انتهت مهلة السداد (15 يوم) — يجب إلغاء الطلب وتقديم استمارة جديدة');
   213	            }
   214	        }
   215	    }
   216	
   217	    /**
   218	     * Record interview results for stage 5.
   219	     */
   220	    public function recordInterview(Member $member, array $data): void
   221	    {
   222	        DB::transaction(function () use ($member, $data) {
   223	            $member->interviews()->create([
   224	                'interviewer_id' => $data['interviewer_id'] ?? auth()->id(),
   225	                'interview_date' => $data['interview_date'] ?? now(),
   226	                'status' => InterviewStatus::COMPLETED,
   227	                'result' => InterviewResult::from($data['result']),
   228	                'notes' => $data['notes'] ?? null,
   229	            ]);
   230	
   231	            $result = InterviewResult::from($data['result']);
   232	
   233	            if ($result === InterviewResult::RECOMMEND_APPROVAL) {
   234	                $this->advanceStage($member, 'المقابلة: توصية بالقبول');
   235	            } elseif ($result === InterviewResult::DEFER) {
   236	                // Reset to stage 4 for re-review
   237	                activity()
   238	                    ->performedOn($member)
   239	                    ->causedBy(auth()->user())
   240	                    ->log('تم تأجيل المقابلة — العودة للمراجعة');
   241	            }
   242	            // Rejection stays at current stage
   243	        });
   244	    }
   245	
   246	    /**
   247	     * Record board decision for stage 6.
   248	     */
   249	    public function recordBoardDecision(Member $member, array $data): void
   250	    {
   251	        DB::transaction(function () use ($member, $data) {
   252	            $member->boardDecisions()->create([
   253	                'decision_date' => $data['decision_date'] ?? now(),
   254	                'result' => BoardDecisionResult::from($data['result']),
   255	                'reference_number' => $data['reference_number'] ?? null,
   256	                'conditions' => $data['conditions'] ?? null,
   257	                'discount_percentage' => $data['discount_percentage'] ?? null,
   258	                'notes' => $data['notes'] ?? null,
   259	                'decided_by' => auth()->id(),
   260	            ]);
   261	
   262	            $result = BoardDecisionResult::from($data['result']);
   263	
   264	            if ($result === BoardDecisionResult::APPROVED) {
   265	                $member->update(['approval_date' => now()]);
   266	                $this->advanceStage($member, 'قرار المجلس: موافقة — بدأت مهلة 15 يوم للسداد');
   267	            } elseif ($result === BoardDecisionResult::REJECTED) {
   268	                $member->update([
   269	                    'membership_status' => MembershipStatus::CANCELLED,
   270	                ]);
   271	
   272	                WorkflowProgress::where('member_id', $member->id)
   273	                    ->where('stage_number', 6)
   274	                    ->update([
   275	                        'status' => WorkflowProgressStatus::REJECTED,
   276	                        'completed_at' => now(),
   277	                        'completed_by' => auth()->id(),
   278	                        'notes' => 'مرفوض من مجلس الإدارة: ' . ($data['notes'] ?? ''),
   279	                    ]);
   280	
   281	                activity()
   282	                    ->performedOn($member)
   283	                    ->causedBy(auth()->user())
   284	                    ->log('قرار المجلس: رفض — تم أرشفة الطلب');
   285	            }
   286	            // Deferred stays at current stage
   287	        });
   288	    }
   289	
   290	    /**
   291	     * Process membership payment for stage 7.
   292	     */
   293	    public function processPayment(Member $member, array $data): void
   294	    {
   295	        // Payment processing is delegated to Financial module
   296	        // This just validates and advances
   297	        $this->advanceStage($member, 'تم سداد رسوم العضوية');
   298	    }
   299	
   300	    /**
   301	     * Activate membership — assigns membership number.
   302	     * CRITICAL: Membership number = payment order, NOT application order.
   303	     */
   304	    public function activateMembership(Member $member): void
   305	    {
   306	        if ($member->membership_status === MembershipStatus::ACTIVE) {
   307	            throw new \RuntimeException('العضوية مفعّلة بالفعل');
   308	        }
   309	
   310	        DB::transaction(function () use ($member) {
   311	            // Assign membership number based on PAYMENT order
   312	            $membershipNumber = $this->numberService->generateMembershipNumber();
   313	
   314	            $member->update([
   315	                'membership_status' => MembershipStatus::ACTIVE,
   316	                'membership_number' => $membershipNumber,
   317	                'payment_date' => $member->payment_date ?? now(),
   318	                'activation_date' => now(),
   319	            ]);
   320	
   321	            // Activate all dependents
   322	            $member->dependents()->update(['status' => 'active']);
   323	
   324	            // Create first annual subscription
   325	            $this->createInitialSubscription($member);
   326	
   327	            activity()
   328	                ->performedOn($member)
   329	                ->causedBy(auth()->user())
   330	                ->withProperties([
   331	                    'membership_number' => $membershipNumber,
   332	                ])
   333	                ->log("تم تفعيل العضوية — رقم العضوية: {$membershipNumber}");
   334	        });
   335	    }
   336	
   337	    /**
   338	     * Suspend membership.
   339	     */
   340	    public function suspendMembership(Member $member, string $reason): void
   341	    {
   342	        DB::transaction(function () use ($member, $reason) {
   343	            $member->update([
   344	                'membership_status' => MembershipStatus::SUSPENDED,
   345	                'suspension_reason' => $reason,
   346	                'suspended_at' => now(),
   347	            ]);
   348	
   349	            activity()
   350	                ->performedOn($member)
   351	                ->causedBy(auth()->user())
   352	                ->withProperties(['reason' => $reason])
   353	                ->log("تم إيقاف العضوية: {$reason}");
   354	        });
   355	    }
   356	
   357	    /**
   358	     * Reactivate a suspended membership.
   359	     */
   360	    public function reactivateMembership(Member $member): void
   361	    {
   362	        if ($member->membership_status !== MembershipStatus::SUSPENDED) {
   363	            throw new \RuntimeException('العضوية ليست موقوفة');
   364	        }
   365	
   366	        DB::transaction(function () use ($member) {
   367	            $member->update([
   368	                'membership_status' => MembershipStatus::ACTIVE,
   369	                'suspension_reason' => null,
   370	                'suspended_at' => null,
   371	                'reactivated_at' => now(),
   372	            ]);
   373	
   374	            activity()
   375	                ->performedOn($member)
   376	                ->causedBy(auth()->user())
   377	                ->log('تم إعادة تفعيل العضوية');
   378	        });
   379	    }
   380	
   381	    /**
   382	     * Freeze membership (member-requested hold).
   383	     */
   384	    public function freezeMembership(Member $member, string $reason, ?\DateTimeInterface $until = null): void
   385	    {
   386	        DB::transaction(function () use ($member, $reason, $until) {
   387	            $member->update([
   388	                'membership_status' => MembershipStatus::FROZEN,
   389	                'freeze_reason' => $reason,
   390	                'frozen_at' => now(),
   391	                'frozen_until' => $until,
   392	            ]);
   393	
   394	            activity()
   395	                ->performedOn($member)
   396	                ->causedBy(auth()->user())
   397	                ->withProperties([
   398	                    'reason' => $reason,
   399	                    'until' => $until?->format('Y-m-d'),
   400	                ])
   401	                ->log("تم تجميد العضوية: {$reason}");
   402	        });
   403	    }
   404	
   405	    /**
   406	     * Cancel membership permanently.
   407	     */
   408	    public function cancelMembership(Member $member, string $reason): void
   409	    {
   410	        DB::transaction(function () use ($member, $reason) {
   411	            $member->update([
   412	                'membership_status' => MembershipStatus::CANCELLED,
   413	                'cancellation_reason' => $reason,
   414	                'cancelled_at' => now(),
   415	            ]);
   416	
   417	            // Cancel all active subscriptions
   418	            $member->subscriptions()
   419	                ->where('status', SubscriptionStatus::ACTIVE)
   420	                ->update(['status' => SubscriptionStatus::CANCELLED]);
   421	
   422	            // Deactivate dependents
   423	            $member->dependents()->update(['status' => 'inactive']);
   424	
   425	            // Invalidate cards
   426	            $member->cards()->update(['status' => 'cancelled']);
   427	
   428	            activity()
   429	                ->performedOn($member)
   430	                ->causedBy(auth()->user())
   431	                ->withProperties(['reason' => $reason])
   432	                ->log("تم إلغاء العضوية: {$reason}");
   433	        });
   434	    }
   435	
   436	    /**
   437	     * Mark member as deceased.
   438	     */
   439	    public function markDeceased(Member $member, string $deathDate, ?string $notes = null): void
   440	    {
   441	        DB::transaction(function () use ($member, $deathDate, $notes) {
   442	            $member->update([
   443	                'membership_status' => MembershipStatus::DECEASED,
   444	                'death_date' => $deathDate,
   445	                'notes' => $member->notes . "\n\n[وفاة] " . ($notes ?? ''),
   446	            ]);
   447	
   448	            activity()
   449	                ->performedOn($member)
   450	                ->causedBy(auth()->user())
   451	                ->withProperties(['death_date' => $deathDate])
   452	                ->log('تم تسجيل وفاة العضو');
   453	        });
   454	    }
   455	
   456	    /**
   457	     * Create the initial annual subscription upon activation.
   458	     */
   459	    private function createInitialSubscription(Member $member): void
   460	    {
   461	        $currentYear = now()->year;
   462	        $subscriptionAmount = $member->membershipType?->annual_subscription_amount ?? 0;
   463	
   464	        $member->subscriptions()->create([
   465	            'fiscal_year' => $currentYear,
   466	            'amount' => $subscriptionAmount,
   467	            'paid_amount' => $subscriptionAmount, // Paid with membership fees
   468	            'status' => SubscriptionStatus::ACTIVE,
   469	            'start_date' => now()->startOfYear(),
   470	            'end_date' => now()->endOfYear(),
   471	            'payment_date' => now(),
   472	        ]);
   473	    }
   474	
   475	    /**
   476	     * Check and expire overdue applications (15-day rule).
   477	     * Run via scheduled command.
   478	     */
   479	    public function expireOverdueApplications(): int
   480	    {
   481	        $overdueMembers = Member::where('workflow_stage', 7)
   482	            ->where('membership_status', MembershipStatus::PENDING)
   483	            ->whereNotNull('approval_date')
   484	            ->where('approval_date', '<=', now()->subDays(15))
   485	            ->get();
   486	
   487	        $count = 0;
   488	        foreach ($overdueMembers as $member) {
   489	            DB::transaction(function () use ($member) {
   490	                $member->update([
   491	                    'membership_status' => MembershipStatus::EXPIRED,
   492	                ]);
   493	
   494	                WorkflowProgress::where('member_id', $member->id)
   495	                    ->where('stage_number', 7)
   496	                    ->update([
   497	                        'status' => WorkflowProgressStatus::EXPIRED,
   498	                        'notes' => 'انتهت مهلة السداد (15 يوم) تلقائياً',
   499	                    ]);
   500	
   501	                activity()
   502	                    ->performedOn($member)
   503	                    ->log('انتهت مهلة السداد تلقائياً — يتطلب استمارة جديدة');
   504	            });
   505	            $count++;
   506	        }
   507	
   508	        return $count;
   509	    }
   510	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [366]: app/Services/Membership/MembershipWorkflowService.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [367/494]: app/Services/Notifications/NotificationService.php
│ LANGUAGE: php | LINES: 58 | SIZE: 1719 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Services\Notifications;
     4	
     5	use App\Models\Member;
     6	use App\Models\NotificationTemplate;
     7	use Illuminate\Support\Facades\Notification;
     8	
     9	class NotificationService
    10	{
    11	    public function sendToMember(Member $member, string $templateKey, array $variables = []): void
    12	    {
    13	        $template = NotificationTemplate::where('template_key', $templateKey)
    14	            ->where('is_active', true)
    15	            ->first();
    16	
    17	        if (!$template) {
    18	            return;
    19	        }
    20	
    21	        $body = $this->replaceVariables($template->body_ar, $variables);
    22	        $subject = $this->replaceVariables($template->subject_ar, $variables);
    23	
    24	        activity()
    25	            ->performedOn($member)
    26	            ->causedBy(auth()->user())
    27	            ->withProperties([
    28	                'template' => $templateKey,
    29	                'channel' => $template->channel->value,
    30	                'subject' => $subject,
    31	            ])
    32	            ->log('تم إرسال إشعار');
    33	    }
    34	
    35	    public function sendBulk(array $memberIds, string $templateKey, array $variables = []): int
    36	    {
    37	        $sent = 0;
    38	        $members = Member::whereIn('id', $memberIds)->get();
    39	
    40	        foreach ($members as $member) {
    41	            $this->sendToMember($member, $templateKey, array_merge($variables, [
    42	                'member_name' => $member->full_name_ar,
    43	                'membership_number' => $member->membership_number,
    44	            ]));
    45	            $sent++;
    46	        }
    47	
    48	        return $sent;
    49	    }
    50	
    51	    protected function replaceVariables(string $text, array $variables): string
    52	    {
    53	        foreach ($variables as $key => $value) {
    54	            $text = str_replace("{{$key}}", (string) $value, $text);
    55	        }
    56	
    57	        return $text;
    58	    }
    59	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [367]: app/Services/Notifications/NotificationService.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [368/494]: app/Services/Reporting/DashboardStatisticsService.php
│ LANGUAGE: php | LINES: 286 | SIZE: 11230 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Services\Reporting;
     4	
     5	use App\Enums\MembershipStatus;
     6	use App\Enums\ReceiptStatus;
     7	use App\Enums\PaymentStatus;
     8	use App\Enums\SubscriptionStatus;
     9	use App\Enums\ViolationStatus;
    10	use App\Enums\ViolationSeverity;
    11	use App\Enums\BookingStatus;
    12	use App\Enums\CardStatus;
    13	use App\Enums\DependentStatus;
    14	use App\Enums\SuspensionStatus;
    15	use App\Enums\InstallmentStatus;
    16	use App\Models\Member;
    17	use App\Models\Dependent;
    18	use App\Models\Receipt;
    19	use App\Models\ReceiptItem;
    20	use App\Models\Subscription;
    21	use App\Models\Violation;
    22	use App\Models\MemberSuspension;
    23	use App\Models\MemberCard;
    24	use App\Models\InstallmentPlan;
    25	use App\Models\Installment;
    26	use App\Models\FacilityBooking;
    27	use App\Models\WorkflowProgress;
    28	use Illuminate\Support\Carbon;
    29	use Illuminate\Support\Facades\DB;
    30	use Illuminate\Support\Facades\Cache;
    31	
    32	class DashboardStatisticsService
    33	{
    34	    private const CACHE_TTL = 300; // 5 minutes
    35	
    36	    public function getOverviewStats(): array
    37	    {
    38	        return Cache::remember('dashboard.overview_stats', self::CACHE_TTL, function () {
    39	            return [
    40	                'total_members' => Member::count(),
    41	                'active_members' => Member::where('membership_status', MembershipStatus::Active)->count(),
    42	                'pending_members' => Member::whereIn('membership_status', [
    43	                    MembershipStatus::ApplicationPending,
    44	                    MembershipStatus::UnderReview,
    45	                    MembershipStatus::InterviewScheduled,
    46	                    MembershipStatus::BoardReviewPending,
    47	                ])->count(),
    48	                'suspended_members' => Member::where('membership_status', MembershipStatus::Suspended)->count(),
    49	                'frozen_members' => Member::where('membership_status', MembershipStatus::Frozen)->count(),
    50	                'total_dependents' => Dependent::count(),
    51	                'active_dependents' => Dependent::where('status', DependentStatus::Active)->count(),
    52	                'total_revenue_this_month' => $this->getRevenueForPeriod(
    53	                    Carbon::now()->startOfMonth(),
    54	                    Carbon::now()->endOfMonth()
    55	                ),
    56	                'total_revenue_this_year' => $this->getRevenueForPeriod(
    57	                    Carbon::now()->startOfYear(),
    58	                    Carbon::now()->endOfYear()
    59	                ),
    60	                'outstanding_balance' => $this->getOutstandingBalance(),
    61	                'pending_violations' => Violation::whereIn('status', [
    62	                    ViolationStatus::Reported,
    63	                    ViolationStatus::UnderInvestigation,
    64	                ])->count(),
    65	                'active_suspensions' => MemberSuspension::where('status', SuspensionStatus::Active)->count(),
    66	                'expiring_subscriptions_30_days' => Subscription::where('status', SubscriptionStatus::Active)
    67	                    ->whereBetween('end_date', [Carbon::now(), Carbon::now()->addDays(30)])
    68	                    ->count(),
    69	            ];
    70	        });
    71	    }
    72	
    73	    public function getMembershipGrowthData(int $months = 12): array
    74	    {
    75	        $cacheKey = "dashboard.membership_growth.{$months}";
    76	
    77	        return Cache::remember($cacheKey, self::CACHE_TTL, function () use ($months) {
    78	            $startDate = Carbon::now()->subMonths($months - 1)->startOfMonth();
    79	            $data = [];
    80	
    81	            for ($i = 0; $i < $months; $i++) {
    82	                $monthStart = $startDate->copy()->addMonths($i)->startOfMonth();
    83	                $monthEnd = $monthStart->copy()->endOfMonth();
    84	                $monthLabel = $monthStart->translatedFormat('M Y');
    85	
    86	                $newMembers = Member::whereBetween('join_date', [$monthStart, $monthEnd])->count();
    87	                $totalActive = Member::where('membership_status', MembershipStatus::Active)
    88	                    ->where('join_date', '<=', $monthEnd)
    89	                    ->count();
    90	
    91	                $data[] = [
    92	                    'month' => $monthLabel,
    93	                    'month_key' => $monthStart->format('Y-m'),
    94	                    'new_members' => $newMembers,
    95	                    'total_active' => $totalActive,
    96	                ];
    97	            }
    98	
    99	            return $data;
   100	        });
   101	    }
   102	
   103	    public function getRevenueChartData(int $months = 12): array
   104	    {
   105	        $cacheKey = "dashboard.revenue_chart.{$months}";
   106	
   107	        return Cache::remember($cacheKey, self::CACHE_TTL, function () use ($months) {
   108	            $startDate = Carbon::now()->subMonths($months - 1)->startOfMonth();
   109	            $data = [];
   110	
   111	            for ($i = 0; $i < $months; $i++) {
   112	                $monthStart = $startDate->copy()->addMonths($i)->startOfMonth();
   113	                $monthEnd = $monthStart->copy()->endOfMonth();
   114	
   115	                $revenue = Receipt::where('status', ReceiptStatus::Paid)
   116	                    ->whereBetween('receipt_date', [$monthStart, $monthEnd])
   117	                    ->sum('total_amount');
   118	
   119	                $data[] = [
   120	                    'month' => $monthStart->translatedFormat('M Y'),
   121	                    'month_key' => $monthStart->format('Y-m'),
   122	                    'revenue' => (float) $revenue,
   123	                ];
   124	            }
   125	
   126	            return $data;
   127	        });
   128	    }
   129	
   130	    public function getMembershipStatusDistribution(): array
   131	    {
   132	        return Cache::remember('dashboard.membership_status_dist', self::CACHE_TTL, function () {
   133	            $results = Member::select('membership_status', DB::raw('COUNT(*) as count'))
   134	                ->groupBy('membership_status')
   135	                ->get();
   136	
   137	            $distribution = [];
   138	            foreach (MembershipStatus::cases() as $status) {
   139	                $found = $results->firstWhere('membership_status', $status);
   140	                $count = $found ? $found->count : 0;
   141	                if ($count > 0) {
   142	                    $distribution[] = [
   143	                        'status' => $status->value,
   144	                        'label' => $status->getLabel(),
   145	                        'count' => $count,
   146	                        'color' => $status->getColor(),
   147	                    ];
   148	                }
   149	            }
   150	
   151	            return $distribution;
   152	        });
   153	    }
   154	
   155	    public function getRecentMembers(int $limit = 10): \Illuminate\Database\Eloquent\Collection
   156	    {
   157	        return Member::with(['membershipType'])
   158	            ->orderByDesc('created_at')
   159	            ->limit($limit)
   160	            ->get();
   161	    }
   162	
   163	    public function getUpcomingExpirations(int $days = 30, int $limit = 15): \Illuminate\Database\Eloquent\Collection
   164	    {
   165	        return Subscription::with(['member', 'subscriptionPlan'])
   166	            ->where('status', SubscriptionStatus::Active)
   167	            ->whereBetween('end_date', [Carbon::now(), Carbon::now()->addDays($days)])
   168	            ->orderBy('end_date')
   169	            ->limit($limit)
   170	            ->get();
   171	    }
   172	
   173	    public function getPendingWorkflowItems(int $limit = 15): \Illuminate\Database\Eloquent\Collection
   174	    {
   175	        return WorkflowProgress::with(['member'])
   176	            ->where('is_current_stage', true)
   177	            ->whereNull('completed_at')
   178	            ->orderBy('created_at')
   179	            ->limit($limit)
   180	            ->get();
   181	    }
   182	
   183	    public function getOverdueInstallments(int $limit = 15): \Illuminate\Database\Eloquent\Collection
   184	    {
   185	        return Installment::with(['installmentPlan.member'])
   186	            ->where('status', InstallmentStatus::Pending)
   187	            ->where('due_date', '<', Carbon::now())
   188	            ->orderBy('due_date')
   189	            ->limit($limit)
   190	            ->get();
   191	    }
   192	
   193	    public function getRevenueByCategoryThisMonth(): array
   194	    {
   195	        return Cache::remember('dashboard.revenue_by_category_month', self::CACHE_TTL, function () {
   196	            $monthStart = Carbon::now()->startOfMonth();
   197	            $monthEnd = Carbon::now()->endOfMonth();
   198	
   199	            return ReceiptItem::join('receipts', 'receipt_items.receipt_id', '=', 'receipts.id')
   200	                ->where('receipts.status', ReceiptStatus::Paid)
   201	                ->whereBetween('receipts.receipt_date', [$monthStart, $monthEnd])
   202	                ->select('receipt_items.fee_category', DB::raw('SUM(receipt_items.total_price) as total'))
   203	                ->groupBy('receipt_items.fee_category')
   204	                ->get()
   205	                ->map(fn ($item) => [
   206	                    'category' => $item->fee_category,
   207	                    'total' => (float) $item->total,
   208	                ])
   209	                ->toArray();
   210	        });
   211	    }
   212	
   213	    public function getSubscriptionStatusSummary(): array
   214	    {
   215	        return Cache::remember('dashboard.subscription_status', self::CACHE_TTL, function () {
   216	            return Subscription::select('status', DB::raw('COUNT(*) as count'))
   217	                ->groupBy('status')
   218	                ->get()
   219	                ->map(fn ($item) => [
   220	                    'status' => $item->status->value ?? $item->status,
   221	                    'label' => $item->status instanceof SubscriptionStatus ? $item->status->getLabel() : $item->status,
   222	                    'count' => $item->count,
   223	                ])
   224	                ->toArray();
   225	        });
   226	    }
   227	
   228	    public function getViolationsSummary(): array
   229	    {
   230	        return Cache::remember('dashboard.violations_summary', self::CACHE_TTL, function () {
   231	            $bySeverity = Violation::select('severity', DB::raw('COUNT(*) as count'))
   232	                ->groupBy('severity')
   233	                ->get()
   234	                ->mapWithKeys(fn ($item) => [$item->severity->value ?? $item->severity => $item->count])
   235	                ->toArray();
   236	
   237	            $byStatus = Violation::select('status', DB::raw('COUNT(*) as count'))
   238	                ->groupBy('status')
   239	                ->get()
   240	                ->mapWithKeys(fn ($item) => [$item->status->value ?? $item->status => $item->count])
   241	                ->toArray();
   242	
   243	            return [
   244	                'by_severity' => $bySeverity,
   245	                'by_status' => $byStatus,
   246	                'total_this_month' => Violation::whereBetween('violation_date', [
   247	                    Carbon::now()->startOfMonth(),
   248	                    Carbon::now()->endOfMonth(),
   249	                ])->count(),
   250	                'total_this_year' => Violation::whereBetween('violation_date', [
   251	                    Carbon::now()->startOfYear(),
   252	                    Carbon::now()->endOfYear(),
   253	                ])->count(),
   254	            ];
   255	        });
   256	    }
   257	
   258	    private function getRevenueForPeriod(Carbon $start, Carbon $end): float
   259	    {
   260	        return (float) Receipt::where('status', ReceiptStatus::Paid)
   261	            ->whereBetween('receipt_date', [$start, $end])
   262	            ->sum('total_amount');
   263	    }
   264	
   265	    private function getOutstandingBalance(): float
   266	    {
   267	        return (float) Receipt::whereIn('status', [ReceiptStatus::Draft, ReceiptStatus::Issued])
   268	            ->sum('total_amount');
   269	    }
   270	
   271	    public function clearCache(): void
   272	    {
   273	        $keys = [
   274	            'dashboard.overview_stats',
   275	            'dashboard.membership_growth.12',
   276	            'dashboard.revenue_chart.12',
   277	            'dashboard.membership_status_dist',
   278	            'dashboard.revenue_by_category_month',
   279	            'dashboard.subscription_status',
   280	            'dashboard.violations_summary',
   281	        ];
   282	
   283	        foreach ($keys as $key) {
   284	            Cache::forget($key);
   285	        }
   286	    }
   287	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [368]: app/Services/Reporting/DashboardStatisticsService.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [369/494]: app/Services/Reporting/FinancialReportService.php
│ LANGUAGE: php | LINES: 254 | SIZE: 9091 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Services\Reporting;
     4	
     5	use App\Enums\ReceiptStatus;
     6	use App\Enums\PaymentStatus;
     7	use App\Enums\FeeCategory;
     8	use App\Enums\InstallmentStatus;
     9	use App\Enums\InstallmentPlanStatus;
    10	use App\Models\Receipt;
    11	use App\Models\ReceiptItem;
    12	use App\Models\Installment;
    13	use App\Models\InstallmentPlan;
    14	use App\Models\Member;
    15	use Illuminate\Support\Carbon;
    16	use Illuminate\Support\Facades\DB;
    17	use Illuminate\Database\Eloquent\Builder;
    18	
    19	class FinancialReportService
    20	{
    21	    public function getReceiptsQuery(array $filters = []): Builder
    22	    {
    23	        $query = Receipt::with(['member', 'createdByUser']);
    24	
    25	        if (! empty($filters['status'])) {
    26	            $query->where('status', $filters['status']);
    27	        }
    28	
    29	        if (! empty($filters['payment_status'])) {
    30	            $query->where('payment_status', $filters['payment_status']);
    31	        }
    32	
    33	        if (! empty($filters['date_from'])) {
    34	            $query->where('receipt_date', '>=', $filters['date_from']);
    35	        }
    36	
    37	        if (! empty($filters['date_to'])) {
    38	            $query->where('receipt_date', '<=', $filters['date_to']);
    39	        }
    40	
    41	        if (! empty($filters['member_id'])) {
    42	            $query->where('member_id', $filters['member_id']);
    43	        }
    44	
    45	        if (! empty($filters['amount_from'])) {
    46	            $query->where('total_amount', '>=', $filters['amount_from']);
    47	        }
    48	
    49	        if (! empty($filters['amount_to'])) {
    50	            $query->where('total_amount', '<=', $filters['amount_to']);
    51	        }
    52	
    53	        if (! empty($filters['payment_method'])) {
    54	            $query->where('payment_method', $filters['payment_method']);
    55	        }
    56	
    57	        return $query->orderByDesc('receipt_date');
    58	    }
    59	
    60	    public function getRevenueByCategory(Carbon $from, Carbon $to): array
    61	    {
    62	        return ReceiptItem::join('receipts', 'receipt_items.receipt_id', '=', 'receipts.id')
    63	            ->where('receipts.status', ReceiptStatus::Paid)
    64	            ->whereBetween('receipts.receipt_date', [$from, $to])
    65	            ->select('receipt_items.fee_category', DB::raw('SUM(receipt_items.total_price) as total'))
    66	            ->groupBy('receipt_items.fee_category')
    67	            ->get()
    68	            ->map(fn ($item) => [
    69	                'category' => $item->fee_category,
    70	                'label' => $item->fee_category instanceof FeeCategory
    71	                    ? $item->fee_category->getLabel()
    72	                    : ($item->fee_category ?? 'غير محدد'),
    73	                'total' => (float) $item->total,
    74	            ])
    75	            ->toArray();
    76	    }
    77	
    78	    public function getRevenueByPaymentMethod(Carbon $from, Carbon $to): array
    79	    {
    80	        return Receipt::where('status', ReceiptStatus::Paid)
    81	            ->whereBetween('receipt_date', [$from, $to])
    82	            ->select('payment_method', DB::raw('SUM(total_amount) as total'), DB::raw('COUNT(*) as count'))
    83	            ->groupBy('payment_method')
    84	            ->get()
    85	            ->map(fn ($item) => [
    86	                'method' => $item->payment_method ?? 'غير محدد',
    87	                'total' => (float) $item->total,
    88	                'count' => $item->count,
    89	            ])
    90	            ->toArray();
    91	    }
    92	
    93	    public function getDailyRevenueReport(Carbon $from, Carbon $to): array
    94	    {
    95	        return Receipt::where('status', ReceiptStatus::Paid)
    96	            ->whereBetween('receipt_date', [$from, $to])
    97	            ->select(DB::raw('DATE(receipt_date) as day'), DB::raw('SUM(total_amount) as total'), DB::raw('COUNT(*) as count'))
    98	            ->groupBy('day')
    99	            ->orderBy('day')
   100	            ->get()
   101	            ->map(fn ($item) => [
   102	                'date' => $item->day,
   103	                'total' => (float) $item->total,
   104	                'count' => $item->count,
   105	            ])
   106	            ->toArray();
   107	    }
   108	
   109	    public function getMonthlyRevenueComparison(int $year1, int $year2): array
   110	    {
   111	        $data = [];
   112	
   113	        for ($month = 1; $month <= 12; $month++) {
   114	            $rev1 = Receipt::where('status', ReceiptStatus::Paid)
   115	                ->whereYear('receipt_date', $year1)
   116	                ->whereMonth('receipt_date', $month)
   117	                ->sum('total_amount');
   118	
   119	            $rev2 = Receipt::where('status', ReceiptStatus::Paid)
   120	                ->whereYear('receipt_date', $year2)
   121	                ->whereMonth('receipt_date', $month)
   122	                ->sum('total_amount');
   123	
   124	            $data[] = [
   125	                'month' => $month,
   126	                'month_name' => Carbon::create()->month($month)->translatedFormat('F'),
   127	                "year_{$year1}" => (float) $rev1,
   128	                "year_{$year2}" => (float) $rev2,
   129	                'change_pct' => $rev1 > 0
   130	                    ? round((($rev2 - $rev1) / $rev1) * 100, 2)
   131	                    : ($rev2 > 0 ? 100 : 0),
   132	            ];
   133	        }
   134	
   135	        return $data;
   136	    }
   137	
   138	    public function getOutstandingBalancesReport(): Builder
   139	    {
   140	        return Receipt::with(['member'])
   141	            ->whereIn('status', [ReceiptStatus::Draft, ReceiptStatus::Issued])
   142	            ->orderByDesc('total_amount');
   143	    }
   144	
   145	    public function getAgingReport(): array
   146	    {
   147	        $now = Carbon::now();
   148	
   149	        $brackets = [
   150	            '0-30' => [0, 30],
   151	            '31-60' => [31, 60],
   152	            '61-90' => [61, 90],
   153	            '91-120' => [91, 120],
   154	            '120+' => [121, 9999],
   155	        ];
   156	
   157	        $results = [];
   158	        foreach ($brackets as $label => [$minDays, $maxDays]) {
   159	            $dateFrom = $now->copy()->subDays($maxDays)->startOfDay();
   160	            $dateTo = $now->copy()->subDays($minDays)->endOfDay();
   161	
   162	            $data = Receipt::whereIn('status', [ReceiptStatus::Draft, ReceiptStatus::Issued])
   163	                ->whereBetween('receipt_date', [$dateFrom, $dateTo])
   164	                ->selectRaw('COUNT(*) as count, SUM(total_amount) as total')
   165	                ->first();
   166	
   167	            $results[] = [
   168	                'bracket' => $label,
   169	                'count' => $data->count ?? 0,
   170	                'total' => (float) ($data->total ?? 0),
   171	            ];
   172	        }
   173	
   174	        return $results;
   175	    }
   176	
   177	    public function getInstallmentReport(array $filters = []): Builder
   178	    {
   179	        $query = InstallmentPlan::with(['member', 'installments']);
   180	
   181	        if (! empty($filters['status'])) {
   182	            $query->where('status', $filters['status']);
   183	        }
   184	
   185	        if (! empty($filters['member_id'])) {
   186	            $query->where('member_id', $filters['member_id']);
   187	        }
   188	
   189	        return $query->orderByDesc('created_at');
   190	    }
   191	
   192	    public function getOverdueInstallmentsReport(): Builder
   193	    {
   194	        return Installment::with(['installmentPlan.member'])
   195	            ->where('status', InstallmentStatus::Pending)
   196	            ->where('due_date', '<', Carbon::now())
   197	            ->orderBy('due_date');
   198	    }
   199	
   200	    public function getCollectionSummary(Carbon $from, Carbon $to): array
   201	    {
   202	        $totalInvoiced = Receipt::whereBetween('receipt_date', [$from, $to])
   203	            ->sum('total_amount');
   204	
   205	        $totalCollected = Receipt::where('status', ReceiptStatus::Paid)
   206	            ->whereBetween('receipt_date', [$from, $to])
   207	            ->sum('total_amount');
   208	
   209	        $totalOutstanding = Receipt::whereIn('status', [ReceiptStatus::Draft, ReceiptStatus::Issued])
   210	            ->whereBetween('receipt_date', [$from, $to])
   211	            ->sum('total_amount');
   212	
   213	        $totalCancelled = Receipt::where('status', ReceiptStatus::Cancelled)
   214	            ->whereBetween('receipt_date', [$from, $to])
   215	            ->sum('total_amount');
   216	
   217	        $receiptCount = Receipt::where('status', ReceiptStatus::Paid)
   218	            ->whereBetween('receipt_date', [$from, $to])
   219	            ->count();
   220	
   221	        return [
   222	            'total_invoiced' => (float) $totalInvoiced,
   223	            'total_collected' => (float) $totalCollected,
   224	            'total_outstanding' => (float) $totalOutstanding,
   225	            'total_cancelled' => (float) $totalCancelled,
   226	            'collection_rate' => $totalInvoiced > 0
   227	                ? round(($totalCollected / $totalInvoiced) * 100, 2)
   228	                : 0,
   229	            'receipt_count' => $receiptCount,
   230	            'avg_receipt_amount' => $receiptCount > 0
   231	                ? round($totalCollected / $receiptCount, 2)
   232	                : 0,
   233	        ];
   234	    }
   235	
   236	    public function getTopPayingMembers(Carbon $from, Carbon $to, int $limit = 20): array
   237	    {
   238	        return Receipt::where('status', ReceiptStatus::Paid)
   239	            ->whereBetween('receipt_date', [$from, $to])
   240	            ->select('member_id', DB::raw('SUM(total_amount) as total_paid'), DB::raw('COUNT(*) as receipt_count'))
   241	            ->groupBy('member_id')
   242	            ->orderByDesc('total_paid')
   243	            ->limit($limit)
   244	            ->with('member')
   245	            ->get()
   246	            ->map(fn ($item) => [
   247	                'member_id' => $item->member_id,
   248	                'member_name' => $item->member?->full_name_ar ?? 'غير معروف',
   249	                'membership_number' => $item->member?->membership_number,
   250	                'total_paid' => (float) $item->total_paid,
   251	                'receipt_count' => $item->receipt_count,
   252	            ])
   253	            ->toArray();
   254	    }
   255	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [369]: app/Services/Reporting/FinancialReportService.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [370/494]: app/Services/Reporting/MembershipReportService.php
│ LANGUAGE: php | LINES: 206 | SIZE: 7279 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Services\Reporting;
     4	
     5	use App\Enums\MembershipStatus;
     6	use App\Enums\Gender;
     7	use App\Enums\DependentStatus;
     8	use App\Models\Member;
     9	use App\Models\Dependent;
    10	use App\Models\MembershipType;
    11	use Illuminate\Support\Carbon;
    12	use Illuminate\Support\Facades\DB;
    13	use Illuminate\Database\Eloquent\Builder;
    14	
    15	class MembershipReportService
    16	{
    17	    public function getMembersQuery(array $filters = []): Builder
    18	    {
    19	        $query = Member::with(['membershipType', 'dependents']);
    20	
    21	        if (! empty($filters['membership_status'])) {
    22	            $query->where('membership_status', $filters['membership_status']);
    23	        }
    24	
    25	        if (! empty($filters['membership_type_id'])) {
    26	            $query->where('membership_type_id', $filters['membership_type_id']);
    27	        }
    28	
    29	        if (! empty($filters['gender'])) {
    30	            $query->where('gender', $filters['gender']);
    31	        }
    32	
    33	        if (! empty($filters['join_date_from'])) {
    34	            $query->where('join_date', '>=', $filters['join_date_from']);
    35	        }
    36	
    37	        if (! empty($filters['join_date_to'])) {
    38	            $query->where('join_date', '<=', $filters['join_date_to']);
    39	        }
    40	
    41	        if (! empty($filters['age_from'])) {
    42	            $query->where('date_of_birth', '<=', Carbon::now()->subYears($filters['age_from']));
    43	        }
    44	
    45	        if (! empty($filters['age_to'])) {
    46	            $query->where('date_of_birth', '>=', Carbon::now()->subYears($filters['age_to']));
    47	        }
    48	
    49	        if (! empty($filters['has_dependents'])) {
    50	            if ($filters['has_dependents'] === 'yes') {
    51	                $query->has('dependents');
    52	            } else {
    53	                $query->doesntHave('dependents');
    54	            }
    55	        }
    56	
    57	        if (! empty($filters['national_id'])) {
    58	            $query->where('national_id', 'LIKE', "%{$filters['national_id']}%");
    59	        }
    60	
    61	        if (! empty($filters['name'])) {
    62	            $query->where(function ($q) use ($filters) {
    63	                $q->where('full_name_ar', 'LIKE', "%{$filters['name']}%")
    64	                    ->orWhere('full_name_en', 'LIKE', "%{$filters['name']}%");
    65	            });
    66	        }
    67	
    68	        return $query->orderBy('membership_number');
    69	    }
    70	
    71	    public function getMembershipSummaryByType(): array
    72	    {
    73	        return MembershipType::withCount([
    74	            'members',
    75	            'members as active_members_count' => fn ($q) => $q->where('membership_status', MembershipStatus::Active),
    76	            'members as suspended_members_count' => fn ($q) => $q->where('membership_status', MembershipStatus::Suspended),
    77	            'members as frozen_members_count' => fn ($q) => $q->where('membership_status', MembershipStatus::Frozen),
    78	        ])->get()->map(fn ($type) => [
    79	            'type_name' => $type->name_ar,
    80	            'total' => $type->members_count,
    81	            'active' => $type->active_members_count,
    82	            'suspended' => $type->suspended_members_count,
    83	            'frozen' => $type->frozen_members_count,
    84	        ])->toArray();
    85	    }
    86	
    87	    public function getAgeDistribution(): array
    88	    {
    89	        $brackets = [
    90	            '18-25' => [18, 25],
    91	            '26-35' => [26, 35],
    92	            '36-45' => [36, 45],
    93	            '46-55' => [46, 55],
    94	            '56-65' => [56, 65],
    95	            '65+' => [65, 200],
    96	        ];
    97	
    98	        $results = [];
    99	        foreach ($brackets as $label => [$min, $max]) {
   100	            $dateMax = Carbon::now()->subYears($min)->endOfDay();
   101	            $dateMin = Carbon::now()->subYears($max + 1)->startOfDay();
   102	
   103	            $count = Member::where('membership_status', MembershipStatus::Active)
   104	                ->whereBetween('date_of_birth', [$dateMin, $dateMax])
   105	                ->count();
   106	
   107	            $results[] = [
   108	                'bracket' => $label,
   109	                'count' => $count,
   110	            ];
   111	        }
   112	
   113	        return $results;
   114	    }
   115	
   116	    public function getGenderDistribution(): array
   117	    {
   118	        return Member::where('membership_status', MembershipStatus::Active)
   119	            ->select('gender', DB::raw('COUNT(*) as count'))
   120	            ->groupBy('gender')
   121	            ->get()
   122	            ->map(fn ($item) => [
   123	                'gender' => $item->gender instanceof Gender ? $item->gender->getLabel() : $item->gender,
   124	                'count' => $item->count,
   125	            ])
   126	            ->toArray();
   127	    }
   128	
   129	    public function getNewMembersReport(Carbon $from, Carbon $to): array
   130	    {
   131	        $members = Member::whereBetween('join_date', [$from, $to])
   132	            ->with(['membershipType'])
   133	            ->orderBy('join_date')
   134	            ->get();
   135	
   136	        $byMonth = $members->groupBy(fn ($m) => $m->join_date->format('Y-m'));
   137	        $byType = $members->groupBy(fn ($m) => $m->membershipType?->name_ar ?? 'غير محدد');
   138	
   139	        return [
   140	            'total' => $members->count(),
   141	            'by_month' => $byMonth->map(fn ($group) => $group->count())->toArray(),
   142	            'by_type' => $byType->map(fn ($group) => $group->count())->toArray(),
   143	            'members' => $members,
   144	        ];
   145	    }
   146	
   147	    public function getDependentsSummary(): array
   148	    {
   149	        $total = Dependent::count();
   150	        $active = Dependent::where('status', DependentStatus::Active)->count();
   151	
   152	        $byRelation = Dependent::select('relationship', DB::raw('COUNT(*) as count'))
   153	            ->groupBy('relationship')
   154	            ->get()
   155	            ->mapWithKeys(fn ($item) => [$item->relationship => $item->count])
   156	            ->toArray();
   157	
   158	        $membersWithDependents = Member::has('dependents')->count();
   159	        $avgDependentsPerMember = $membersWithDependents > 0
   160	            ? round($total / $membersWithDependents, 2)
   161	            : 0;
   162	
   163	        return [
   164	            'total' => $total,
   165	            'active' => $active,
   166	            'by_relationship' => $byRelation,
   167	            'members_with_dependents' => $membersWithDependents,
   168	            'avg_per_member' => $avgDependentsPerMember,
   169	        ];
   170	    }
   171	
   172	    public function getChurnReport(int $months = 12): array
   173	    {
   174	        $data = [];
   175	        $startDate = Carbon::now()->subMonths($months - 1)->startOfMonth();
   176	
   177	        for ($i = 0; $i < $months; $i++) {
   178	            $monthStart = $startDate->copy()->addMonths($i)->startOfMonth();
   179	            $monthEnd = $monthStart->copy()->endOfMonth();
   180	
   181	            $resigned = Member::where('membership_status', MembershipStatus::Resigned)
   182	                ->whereBetween('updated_at', [$monthStart, $monthEnd])
   183	                ->count();
   184	
   185	            $cancelled = Member::where('membership_status', MembershipStatus::Cancelled)
   186	                ->whereBetween('updated_at', [$monthStart, $monthEnd])
   187	                ->count();
   188	
   189	            $expired = Member::where('membership_status', MembershipStatus::Expired)
   190	                ->whereBetween('updated_at', [$monthStart, $monthEnd])
   191	                ->count();
   192	
   193	            $newJoined = Member::whereBetween('join_date', [$monthStart, $monthEnd])->count();
   194	
   195	            $data[] = [
   196	                'month' => $monthStart->translatedFormat('M Y'),
   197	                'new' => $newJoined,
   198	                'resigned' => $resigned,
   199	                'cancelled' => $cancelled,
   200	                'expired' => $expired,
   201	                'net_change' => $newJoined - $resigned - $cancelled - $expired,
   202	            ];
   203	        }
   204	
   205	        return $data;
   206	    }
   207	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [370]: app/Services/Reporting/MembershipReportService.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [371/494]: app/Services/Reporting/ReportService.php
│ LANGUAGE: php | LINES: 117 | SIZE: 4304 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Services\Reporting;
     4	
     5	use App\Enums\FeeCategory;
     6	use App\Enums\ReceiptStatus;
     7	use App\Models\FiscalYear;
     8	use App\Models\Member;
     9	use App\Models\MembershipStatus;
    10	use App\Models\MembershipType;
    11	use App\Models\Receipt;
    12	use Illuminate\Support\Collection;
    13	use Illuminate\Support\Facades\DB;
    14	
    15	class ReportService
    16	{
    17	    public function getMembershipStatistics(): array
    18	    {
    19	        $byStatus = Member::select('status_id', DB::raw('COUNT(*) as count'))
    20	            ->groupBy('status_id')
    21	            ->with('status')
    22	            ->get()
    23	            ->mapWithKeys(fn ($item) => [$item->status->name_ar ?? 'غير محدد' => $item->count])
    24	            ->toArray();
    25	
    26	        $byType = Member::select('membership_type_id', DB::raw('COUNT(*) as count'))
    27	            ->groupBy('membership_type_id')
    28	            ->with('membershipType')
    29	            ->get()
    30	            ->mapWithKeys(fn ($item) => [$item->membershipType->name_ar ?? 'غير محدد' => $item->count])
    31	            ->toArray();
    32	
    33	        return [
    34	            'total' => Member::count(),
    35	            'active' => Member::active()->count(),
    36	            'pending' => Member::pending()->count(),
    37	            'by_status' => $byStatus,
    38	            'by_type' => $byType,
    39	            'new_this_month' => Member::whereMonth('created_at', now()->month)
    40	                ->whereYear('created_at', now()->year)
    41	                ->count(),
    42	            'new_this_year' => Member::whereYear('created_at', now()->year)->count(),
    43	        ];
    44	    }
    45	
    46	    public function getFinancialSummary(?int $fiscalYearId = null): array
    47	    {
    48	        $query = Receipt::where('status', ReceiptStatus::Paid);
    49	
    50	        if ($fiscalYearId) {
    51	            $query->where('fiscal_year_id', $fiscalYearId);
    52	        }
    53	
    54	        $totalRevenue = (float) $query->sum('total_amount');
    55	
    56	        $byCategory = Receipt::where('status', ReceiptStatus::Paid)
    57	            ->when($fiscalYearId, fn ($q) => $q->where('fiscal_year_id', $fiscalYearId))
    58	            ->select('fee_category', DB::raw('SUM(total_amount) as total'), DB::raw('COUNT(*) as count'))
    59	            ->groupBy('fee_category')
    60	            ->get()
    61	            ->mapWithKeys(fn ($item) => [
    62	                FeeCategory::tryFrom($item->fee_category)?->getLabel() ?? $item->fee_category => [
    63	                    'total' => (float) $item->total,
    64	                    'count' => $item->count,
    65	                ],
    66	            ])
    67	            ->toArray();
    68	
    69	        return [
    70	            'total_revenue' => $totalRevenue,
    71	            'by_category' => $byCategory,
    72	            'total_receipts' => Receipt::when($fiscalYearId, fn ($q) => $q->where('fiscal_year_id', $fiscalYearId))->count(),
    73	            'paid_receipts' => Receipt::where('status', ReceiptStatus::Paid)
    74	                ->when($fiscalYearId, fn ($q) => $q->where('fiscal_year_id', $fiscalYearId))->count(),
    75	            'cancelled_receipts' => Receipt::where('status', ReceiptStatus::Cancelled)
    76	                ->when($fiscalYearId, fn ($q) => $q->where('fiscal_year_id', $fiscalYearId))->count(),
    77	        ];
    78	    }
    79	
    80	    public function getSubscriptionDefaulters(int $year): Collection
    81	    {
    82	        return Member::active()
    83	            ->where(function ($q) use ($year) {
    84	                $q->whereNull('paid_through_year')
    85	                  ->orWhere('paid_through_year', '<', $year);
    86	            })
    87	            ->with(['membershipType', 'status'])
    88	            ->orderBy('full_name_ar')
    89	            ->get();
    90	    }
    91	
    92	    public function getMonthlyRevenueChart(?int $fiscalYearId = null): array
    93	    {
    94	        $query = Receipt::where('status', ReceiptStatus::Paid)
    95	            ->when($fiscalYearId, fn ($q) => $q->where('fiscal_year_id', $fiscalYearId))
    96	            ->select(
    97	                DB::raw('MONTH(payment_date) as month'),
    98	                DB::raw('SUM(total_amount) as total'),
    99	            )
   100	            ->groupBy(DB::raw('MONTH(payment_date)'))
   101	            ->orderBy('month')
   102	            ->get();
   103	
   104	        $months = [];
   105	        for ($i = 1; $i <= 12; $i++) {
   106	            $months[$i] = [
   107	                'month' => \App\Helpers\DateHelper::arabicMonth($i),
   108	                'total' => 0,
   109	            ];
   110	        }
   111	
   112	        foreach ($query as $row) {
   113	            $months[$row->month]['total'] = (float) $row->total;
   114	        }
   115	
   116	        return array_values($months);
   117	    }
   118	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [371]: app/Services/Reporting/ReportService.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [372/494]: app/Services/Reporting/SubscriptionReportService.php
│ LANGUAGE: php | LINES: 133 | SIZE: 4985 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Services\Reporting;
     4	
     5	use App\Enums\SubscriptionStatus;
     6	use App\Models\Subscription;
     7	use App\Models\SubscriptionPlan;
     8	use Illuminate\Support\Carbon;
     9	use Illuminate\Support\Facades\DB;
    10	use Illuminate\Database\Eloquent\Builder;
    11	
    12	class SubscriptionReportService
    13	{
    14	    public function getSubscriptionsQuery(array $filters = []): Builder
    15	    {
    16	        $query = Subscription::with(['member', 'subscriptionPlan']);
    17	
    18	        if (! empty($filters['status'])) {
    19	            $query->where('status', $filters['status']);
    20	        }
    21	
    22	        if (! empty($filters['subscription_plan_id'])) {
    23	            $query->where('subscription_plan_id', $filters['subscription_plan_id']);
    24	        }
    25	
    26	        if (! empty($filters['start_date_from'])) {
    27	            $query->where('start_date', '>=', $filters['start_date_from']);
    28	        }
    29	
    30	        if (! empty($filters['start_date_to'])) {
    31	            $query->where('start_date', '<=', $filters['start_date_to']);
    32	        }
    33	
    34	        if (! empty($filters['end_date_from'])) {
    35	            $query->where('end_date', '>=', $filters['end_date_from']);
    36	        }
    37	
    38	        if (! empty($filters['end_date_to'])) {
    39	            $query->where('end_date', '<=', $filters['end_date_to']);
    40	        }
    41	
    42	        if (! empty($filters['expiring_within_days'])) {
    43	            $query->where('status', SubscriptionStatus::Active)
    44	                ->whereBetween('end_date', [
    45	                    Carbon::now(),
    46	                    Carbon::now()->addDays($filters['expiring_within_days']),
    47	                ]);
    48	        }
    49	
    50	        return $query->orderByDesc('start_date');
    51	    }
    52	
    53	    public function getSubscriptionSummaryByPlan(): array
    54	    {
    55	        return SubscriptionPlan::withCount([
    56	            'subscriptions',
    57	            'subscriptions as active_count' => fn ($q) => $q->where('status', SubscriptionStatus::Active),
    58	            'subscriptions as expired_count' => fn ($q) => $q->where('status', SubscriptionStatus::Expired),
    59	            'subscriptions as cancelled_count' => fn ($q) => $q->where('status', SubscriptionStatus::Cancelled),
    60	        ])->get()->map(fn ($plan) => [
    61	            'plan_name' => $plan->name_ar,
    62	            'total' => $plan->subscriptions_count,
    63	            'active' => $plan->active_count,
    64	            'expired' => $plan->expired_count,
    65	            'cancelled' => $plan->cancelled_count,
    66	        ])->toArray();
    67	    }
    68	
    69	    public function getExpiringSubscriptions(int $days = 30): Builder
    70	    {
    71	        return Subscription::with(['member', 'subscriptionPlan'])
    72	            ->where('status', SubscriptionStatus::Active)
    73	            ->whereBetween('end_date', [Carbon::now(), Carbon::now()->addDays($days)])
    74	            ->orderBy('end_date');
    75	    }
    76	
    77	    public function getExpiredSubscriptions(): Builder
    78	    {
    79	        return Subscription::with(['member', 'subscriptionPlan'])
    80	            ->where('status', SubscriptionStatus::Expired)
    81	            ->orderByDesc('end_date');
    82	    }
    83	
    84	    public function getRenewalRate(int $months = 12): array
    85	    {
    86	        $data = [];
    87	        $startDate = Carbon::now()->subMonths($months - 1)->startOfMonth();
    88	
    89	        for ($i = 0; $i < $months; $i++) {
    90	            $monthStart = $startDate->copy()->addMonths($i)->startOfMonth();
    91	            $monthEnd = $monthStart->copy()->endOfMonth();
    92	
    93	            $expiredInMonth = Subscription::where('status', '!=', SubscriptionStatus::Active)
    94	                ->whereBetween('end_date', [$monthStart, $monthEnd])
    95	                ->count();
    96	
    97	            $renewedInMonth = Subscription::where('status', SubscriptionStatus::Active)
    98	                ->whereBetween('start_date', [$monthStart, $monthEnd])
    99	                ->whereHas('member', function ($q) use ($monthStart) {
   100	                    $q->where('join_date', '<', $monthStart);
   101	                })
   102	                ->count();
   103	
   104	            $data[] = [
   105	                'month' => $monthStart->translatedFormat('M Y'),
   106	                'expired' => $expiredInMonth,
   107	                'renewed' => $renewedInMonth,
   108	                'rate' => $expiredInMonth > 0
   109	                    ? round(($renewedInMonth / $expiredInMonth) * 100, 2)
   110	                    : 0,
   111	            ];
   112	        }
   113	
   114	        return $data;
   115	    }
   116	
   117	    public function getRevenueByPlan(Carbon $from, Carbon $to): array
   118	    {
   119	        return Subscription::join('receipts', function ($join) {
   120	            $join->on('subscriptions.member_id', '=', 'receipts.member_id');
   121	        })
   122	            ->where('receipts.status', 'paid')
   123	            ->whereBetween('subscriptions.start_date', [$from, $to])
   124	            ->select('subscriptions.subscription_plan_id', DB::raw('SUM(receipts.total_amount) as total_revenue'))
   125	            ->groupBy('subscriptions.subscription_plan_id')
   126	            ->with('subscriptionPlan')
   127	            ->get()
   128	            ->map(fn ($item) => [
   129	                'plan' => $item->subscriptionPlan?->name_ar ?? 'غير محدد',
   130	                'revenue' => (float) $item->total_revenue,
   131	            ])
   132	            ->toArray();
   133	    }
   134	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [372]: app/Services/Reporting/SubscriptionReportService.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [373/494]: app/Services/Reporting/ViolationReportService.php
│ LANGUAGE: php | LINES: 141 | SIZE: 5003 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Services\Reporting;
     4	
     5	use App\Enums\ViolationStatus;
     6	use App\Enums\ViolationSeverity;
     7	use App\Enums\PenaltyStatus;
     8	use App\Enums\SuspensionStatus;
     9	use App\Models\Violation;
    10	use App\Models\Penalty;
    11	use App\Models\MemberSuspension;
    12	use Illuminate\Support\Carbon;
    13	use Illuminate\Support\Facades\DB;
    14	use Illuminate\Database\Eloquent\Builder;
    15	
    16	class ViolationReportService
    17	{
    18	    public function getViolationsQuery(array $filters = []): Builder
    19	    {
    20	        $query = Violation::with(['member', 'violationType', 'reportedByUser']);
    21	
    22	        if (! empty($filters['status'])) {
    23	            $query->where('status', $filters['status']);
    24	        }
    25	
    26	        if (! empty($filters['severity'])) {
    27	            $query->where('severity', $filters['severity']);
    28	        }
    29	
    30	        if (! empty($filters['violation_type_id'])) {
    31	            $query->where('violation_type_id', $filters['violation_type_id']);
    32	        }
    33	
    34	        if (! empty($filters['member_id'])) {
    35	            $query->where('member_id', $filters['member_id']);
    36	        }
    37	
    38	        if (! empty($filters['date_from'])) {
    39	            $query->where('violation_date', '>=', $filters['date_from']);
    40	        }
    41	
    42	        if (! empty($filters['date_to'])) {
    43	            $query->where('violation_date', '<=', $filters['date_to']);
    44	        }
    45	
    46	        return $query->orderByDesc('violation_date');
    47	    }
    48	
    49	    public function getViolationsByTypeReport(Carbon $from, Carbon $to): array
    50	    {
    51	        return Violation::join('violation_types', 'violations.violation_type_id', '=', 'violation_types.id')
    52	            ->whereBetween('violations.violation_date', [$from, $to])
    53	            ->select('violation_types.name_ar', DB::raw('COUNT(*) as count'))
    54	            ->groupBy('violation_types.name_ar')
    55	            ->orderByDesc('count')
    56	            ->get()
    57	            ->map(fn ($item) => [
    58	                'type' => $item->name_ar,
    59	                'count' => $item->count,
    60	            ])
    61	            ->toArray();
    62	    }
    63	
    64	    public function getViolationsBySeverityReport(Carbon $from, Carbon $to): array
    65	    {
    66	        return Violation::whereBetween('violation_date', [$from, $to])
    67	            ->select('severity', DB::raw('COUNT(*) as count'))
    68	            ->groupBy('severity')
    69	            ->get()
    70	            ->map(fn ($item) => [
    71	                'severity' => $item->severity instanceof ViolationSeverity ? $item->severity->getLabel() : $item->severity,
    72	                'severity_key' => $item->severity instanceof ViolationSeverity ? $item->severity->value : $item->severity,
    73	                'count' => $item->count,
    74	            ])
    75	            ->toArray();
    76	    }
    77	
    78	    public function getRepeatOffendersReport(int $minViolations = 2): array
    79	    {
    80	        return Violation::select('member_id', DB::raw('COUNT(*) as violation_count'))
    81	            ->groupBy('member_id')
    82	            ->having('violation_count', '>=', $minViolations)
    83	            ->orderByDesc('violation_count')
    84	            ->with('member')
    85	            ->get()
    86	            ->map(fn ($item) => [
    87	                'member_id' => $item->member_id,
    88	                'member_name' => $item->member?->full_name_ar ?? 'غير معروف',
    89	                'membership_number' => $item->member?->membership_number,
    90	                'violation_count' => $item->violation_count,
    91	            ])
    92	            ->toArray();
    93	    }
    94	
    95	    public function getActiveSuspensionsReport(): Builder
    96	    {
    97	        return MemberSuspension::with(['member', 'violation'])
    98	            ->where('status', SuspensionStatus::Active)
    99	            ->orderBy('start_date');
   100	    }
   101	
   102	    public function getPenaltySummary(Carbon $from, Carbon $to): array
   103	    {
   104	        $totalPenalties = Penalty::whereBetween('imposed_date', [$from, $to])->count();
   105	        $totalFines = Penalty::whereBetween('imposed_date', [$from, $to])->sum('fine_amount');
   106	
   107	        $byStatus = Penalty::whereBetween('imposed_date', [$from, $to])
   108	            ->select('status', DB::raw('COUNT(*) as count'))
   109	            ->groupBy('status')
   110	            ->get()
   111	            ->mapWithKeys(fn ($item) => [
   112	                ($item->status instanceof PenaltyStatus ? $item->status->getLabel() : $item->status) => $item->count,
   113	            ])
   114	            ->toArray();
   115	
   116	        return [
   117	            'total_penalties' => $totalPenalties,
   118	            'total_fines' => (float) $totalFines,
   119	            'by_status' => $byStatus,
   120	        ];
   121	    }
   122	
   123	    public function getMonthlyViolationTrend(int $months = 12): array
   124	    {
   125	        $data = [];
   126	        $startDate = Carbon::now()->subMonths($months - 1)->startOfMonth();
   127	
   128	        for ($i = 0; $i < $months; $i++) {
   129	            $monthStart = $startDate->copy()->addMonths($i)->startOfMonth();
   130	            $monthEnd = $monthStart->copy()->endOfMonth();
   131	
   132	            $count = Violation::whereBetween('violation_date', [$monthStart, $monthEnd])->count();
   133	
   134	            $data[] = [
   135	                'month' => $monthStart->translatedFormat('M Y'),
   136	                'count' => $count,
   137	            ];
   138	        }
   139	
   140	        return $data;
   141	    }
   142	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [373]: app/Services/Reporting/ViolationReportService.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [374/494]: app/Services/Subscription/RenewalService.php
│ LANGUAGE: php | LINES: 174 | SIZE: 6507 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Services\Subscription;
     4	
     5	use App\Enums\SubscriptionStatus;
     6	use App\Models\Member;
     7	use App\Models\Subscription;
     8	use App\Models\SubscriptionPeriod;
     9	use Illuminate\Support\Collection;
    10	use Illuminate\Support\Facades\Auth;
    11	use Illuminate\Support\Facades\DB;
    12	use Illuminate\Support\Facades\Log;
    13	
    14	class RenewalService
    15	{
    16	    public function __construct(
    17	        protected SubscriptionService $subscriptionService,
    18	        protected SubscriptionCalculatorService $calculator,
    19	    ) {}
    20	
    21	    /**
    22	     * Generate subscriptions for all active members in a given period.
    23	     * Returns count of created, skipped, and errored.
    24	     */
    25	    public function bulkGenerate(SubscriptionPeriod $period): array
    26	    {
    27	        $results = [
    28	            'created' => 0,
    29	            'skipped' => 0,
    30	            'errors'  => [],
    31	        ];
    32	
    33	        // Get all active members who DON'T already have a subscription for this period
    34	        $existingMemberIds = Subscription::where('subscription_period_id', $period->id)
    35	            ->pluck('member_id')
    36	            ->toArray();
    37	
    38	        $activeMembers = Member::where('status', 'active')
    39	            ->whereNotIn('id', $existingMemberIds)
    40	            ->cursor(); // Memory-efficient for large datasets
    41	
    42	        foreach ($activeMembers as $member) {
    43	            try {
    44	                $this->subscriptionService->createSubscription($member, $period);
    45	                $results['created']++;
    46	            } catch (\Throwable $e) {
    47	                $results['errors'][] = [
    48	                    'member_id'   => $member->id,
    49	                    'member_name' => $member->full_name ?? $member->name_ar ?? "#{$member->id}",
    50	                    'error'       => $e->getMessage(),
    51	                ];
    52	                Log::warning("Bulk subscription generation failed for member #{$member->id}: {$e->getMessage()}");
    53	            }
    54	        }
    55	
    56	        $results['skipped'] = count($existingMemberIds);
    57	
    58	        Log::info("Bulk subscription generation for period {$period->year}: " . json_encode($results));
    59	
    60	        return $results;
    61	    }
    62	
    63	    /**
    64	     * Apply late fees to all overdue subscriptions in a period.
    65	     */
    66	    public function applyLateFees(SubscriptionPeriod $period): array
    67	    {
    68	        $results = [
    69	            'applied'  => 0,
    70	            'skipped'  => 0,
    71	            'errors'   => [],
    72	        ];
    73	
    74	        if ($period->is_grace_period_active) {
    75	            return $results; // Grace period still active, do nothing
    76	        }
    77	
    78	        $subscriptions = Subscription::where('subscription_period_id', $period->id)
    79	            ->whereIn('status', [SubscriptionStatus::PENDING, SubscriptionStatus::PARTIAL])
    80	            ->where('is_late', false)
    81	            ->cursor();
    82	
    83	        foreach ($subscriptions as $subscription) {
    84	            try {
    85	                DB::transaction(function () use ($subscription) {
    86	                    $subscription->applyLateFee();
    87	                });
    88	                $results['applied']++;
    89	            } catch (\Throwable $e) {
    90	                $results['errors'][] = [
    91	                    'subscription_id' => $subscription->id,
    92	                    'member_id'       => $subscription->member_id,
    93	                    'error'           => $e->getMessage(),
    94	                ];
    95	            }
    96	        }
    97	
    98	        Log::info("Late fee application for period {$period->year}: " . json_encode($results));
    99	
   100	        return $results;
   101	    }
   102	
   103	    /**
   104	     * Mark overdue subscriptions past the grace deadline.
   105	     */
   106	    public function markOverdue(SubscriptionPeriod $period): int
   107	    {
   108	        if ($period->is_grace_period_active) {
   109	            return 0;
   110	        }
   111	
   112	        return Subscription::where('subscription_period_id', $period->id)
   113	            ->where('status', SubscriptionStatus::PENDING)
   114	            ->where('is_late', false)
   115	            ->update([
   116	                'status'           => SubscriptionStatus::OVERDUE,
   117	                'is_late'          => true,
   118	                'late_applied_date' => now(),
   119	                'updated_by'       => Auth::id(),
   120	            ]);
   121	    }
   122	
   123	    /**
   124	     * Get members eligible for renewal who haven't been generated yet.
   125	     */
   126	    public function getEligibleMembers(SubscriptionPeriod $period): Collection
   127	    {
   128	        $existingMemberIds = Subscription::where('subscription_period_id', $period->id)
   129	            ->pluck('member_id');
   130	
   131	        return Member::where('status', 'active')
   132	            ->whereNotIn('id', $existingMemberIds)
   133	            ->get();
   134	    }
   135	
   136	    /**
   137	     * Get renewal statistics for a period.
   138	     */
   139	    public function getRenewalStats(SubscriptionPeriod $period): array
   140	    {
   141	        $subscriptions = Subscription::where('subscription_period_id', $period->id);
   142	
   143	        $totalActive = Member::where('status', 'active')->count();
   144	        $totalGenerated = (clone $subscriptions)->count();
   145	        $paidCount = (clone $subscriptions)->where('status', SubscriptionStatus::PAID)->count();
   146	        $partialCount = (clone $subscriptions)->where('status', SubscriptionStatus::PARTIAL)->count();
   147	        $overdueCount = (clone $subscriptions)->where('status', SubscriptionStatus::OVERDUE)->count();
   148	        $pendingCount = (clone $subscriptions)->where('status', SubscriptionStatus::PENDING)->count();
   149	        $waivedCount = (clone $subscriptions)->where('status', SubscriptionStatus::WAIVED)->count();
   150	
   151	        $totalExpected = (clone $subscriptions)->sum('net_amount');
   152	        $totalCollected = (clone $subscriptions)->sum('paid_amount');
   153	        $totalOutstanding = (clone $subscriptions)->unpaid()->sum('balance');
   154	
   155	        return [
   156	            'total_active_members' => $totalActive,
   157	            'total_generated'      => $totalGenerated,
   158	            'not_generated'        => max(0, $totalActive - $totalGenerated),
   159	            'paid_count'           => $paidCount,
   160	            'partial_count'        => $partialCount,
   161	            'overdue_count'        => $overdueCount,
   162	            'pending_count'        => $pendingCount,
   163	            'waived_count'         => $waivedCount,
   164	            'total_expected'       => round($totalExpected, 2),
   165	            'total_collected'      => round($totalCollected, 2),
   166	            'total_outstanding'    => round($totalOutstanding, 2),
   167	            'collection_rate'      => $totalGenerated > 0
   168	                ? round(($paidCount / $totalGenerated) * 100, 2)
   169	                : 0,
   170	            'collection_amount_rate' => $totalExpected > 0
   171	                ? round(($totalCollected / $totalExpected) * 100, 2)
   172	                : 0,
   173	        ];
   174	    }
   175	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [374]: app/Services/Subscription/RenewalService.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [375/494]: app/Services/Subscription/SubscriptionCalculatorService.php
│ LANGUAGE: php | LINES: 171 | SIZE: 5425 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Services\Subscription;
     4	
     5	use App\Models\Member;
     6	use App\Models\SubscriptionPeriod;
     7	
     8	class SubscriptionCalculatorService
     9	{
    10	    /**
    11	     * Calculate the full breakdown for a member's subscription in a given period.
    12	     *
    13	     * @return array{
    14	     *     base_amount: float,
    15	     *     dependents_amount: float,
    16	     *     dependents_breakdown: array,
    17	     *     total_amount: float,
    18	     *     discount_amount: float,
    19	     *     late_fee_amount: float,
    20	     *     net_amount: float,
    21	     *     is_late: bool
    22	     * }
    23	     */
    24	    public function calculate(Member $member, SubscriptionPeriod $period, float $discountAmount = 0): array
    25	    {
    26	        $baseAmount = (float) $period->member_fee;
    27	        $dependentsBreakdown = $this->calculateDependentsBreakdown($member, $period);
    28	        $dependentsAmount = collect($dependentsBreakdown)->sum('fee');
    29	        $totalAmount = $baseAmount + $dependentsAmount;
    30	
    31	        $isLate = $this->isLateForPeriod($period);
    32	        $lateFeeAmount = 0.0;
    33	
    34	        if ($isLate) {
    35	            $lateFeeAmount = $period->calculateLateFee($totalAmount);
    36	        }
    37	
    38	        $netAmount = $totalAmount - $discountAmount + $lateFeeAmount;
    39	
    40	        return [
    41	            'base_amount'           => round($baseAmount, 2),
    42	            'dependents_amount'     => round($dependentsAmount, 2),
    43	            'dependents_breakdown'  => $dependentsBreakdown,
    44	            'total_amount'          => round($totalAmount, 2),
    45	            'discount_amount'       => round($discountAmount, 2),
    46	            'late_fee_amount'       => round($lateFeeAmount, 2),
    47	            'net_amount'            => round(max(0, $netAmount), 2),
    48	            'is_late'               => $isLate,
    49	        ];
    50	    }
    51	
    52	    /**
    53	     * Calculate breakdown for each dependent.
    54	     */
    55	    public function calculateDependentsBreakdown(Member $member, SubscriptionPeriod $period): array
    56	    {
    57	        $dependents = $member->dependents()
    58	            ->where('status', 'active')
    59	            ->get();
    60	
    61	        $breakdown = [];
    62	
    63	        foreach ($dependents as $dependent) {
    64	            $fee = $this->getDependentFee($dependent, $period);
    65	
    66	            $breakdown[] = [
    67	                'dependent_id'   => $dependent->id,
    68	                'name'           => $dependent->full_name ?? $dependent->name_ar,
    69	                'relationship'   => $dependent->relationship ?? $dependent->relation_type,
    70	                'fee'            => round($fee, 2),
    71	                'fee_type'       => $this->getDependentFeeType($dependent),
    72	            ];
    73	        }
    74	
    75	        return $breakdown;
    76	    }
    77	
    78	    /**
    79	     * Get the fee for a specific dependent based on their relationship type.
    80	     */
    81	    protected function getDependentFee($dependent, SubscriptionPeriod $period): float
    82	    {
    83	        $relationship = strtolower($dependent->relationship ?? $dependent->relation_type ?? '');
    84	
    85	        // Spouse
    86	        if (in_array($relationship, ['spouse', 'زوج', 'زوجة', 'wife', 'husband'])) {
    87	            return (float) $period->spouse_fee;
    88	        }
    89	
    90	        // Child — check age eligibility
    91	        if (in_array($relationship, ['son', 'daughter', 'child', 'ابن', 'ابنة', 'طفل'])) {
    92	            if ($this->isDependentAgeEligible($dependent)) {
    93	                return (float) $period->child_fee;
    94	            }
    95	            return 0; // Aged out — fee = 0, should be flagged separately
    96	        }
    97	
    98	        // Default: child fee for unknown relationship types
    99	        return (float) $period->child_fee;
   100	    }
   101	
   102	    protected function getDependentFeeType($dependent): string
   103	    {
   104	        $relationship = strtolower($dependent->relationship ?? $dependent->relation_type ?? '');
   105	
   106	        if (in_array($relationship, ['spouse', 'زوج', 'زوجة', 'wife', 'husband'])) {
   107	            return 'spouse';
   108	        }
   109	
   110	        return 'child';
   111	    }
   112	
   113	    /**
   114	     * Check if a dependent (child) is still age-eligible (under 25).
   115	     */
   116	    protected function isDependentAgeEligible($dependent): bool
   117	    {
   118	        $birthDate = $dependent->birth_date ?? $dependent->date_of_birth ?? null;
   119	
   120	        if (!$birthDate) {
   121	            return true; // No birth date = assume eligible (data issue)
   122	        }
   123	
   124	        $age = now()->diffInYears($birthDate);
   125	        return $age < 25;
   126	    }
   127	
   128	    /**
   129	     * Check if we're past the grace period for a subscription period.
   130	     */
   131	    protected function isLateForPeriod(SubscriptionPeriod $period): bool
   132	    {
   133	        if ($period->grace_deadline) {
   134	            return now()->gt($period->grace_deadline);
   135	        }
   136	
   137	        $graceDays = $period->grace_period_days ?? 90;
   138	        $deadline = $period->start_date->addDays($graceDays);
   139	
   140	        return now()->gt($deadline);
   141	    }
   142	
   143	    /**
   144	     * Calculate total outstanding balance for a member across all periods.
   145	     */
   146	    public function calculateOutstandingBalance(Member $member): float
   147	    {
   148	        return (float) $member->subscriptions()
   149	            ->unpaid()
   150	            ->sum('balance');
   151	    }
   152	
   153	    /**
   154	     * Check if member has any overdue subscriptions.
   155	     */
   156	    public function hasOverdueSubscriptions(Member $member): bool
   157	    {
   158	        return $member->subscriptions()
   159	            ->overdue()
   160	            ->exists();
   161	    }
   162	
   163	    /**
   164	     * Get count of overdue years for a member.
   165	     */
   166	    public function getOverdueYearsCount(Member $member): int
   167	    {
   168	        return $member->subscriptions()
   169	            ->overdue()
   170	            ->count();
   171	    }
   172	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [375]: app/Services/Subscription/SubscriptionCalculatorService.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [376/494]: app/Services/Subscription/SubscriptionService.php
│ LANGUAGE: php | LINES: 200 | SIZE: 8364 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace App\Services\Subscription;
     4	
     5	use App\Enums\SubscriptionStatus;
     6	use App\Models\Member;
     7	use App\Models\Subscription;
     8	use App\Models\SubscriptionPayment;
     9	use App\Models\SubscriptionPeriod;
    10	use Illuminate\Support\Collection;
    11	use Illuminate\Support\Facades\Auth;
    12	use Illuminate\Support\Facades\DB;
    13	
    14	class SubscriptionService
    15	{
    16	    public function __construct(
    17	        protected SubscriptionCalculatorService $calculator,
    18	    ) {}
    19	
    20	    /**
    21	     * Create a subscription for a member in a given period.
    22	     */
    23	    public function createSubscription(
    24	        Member $member,
    25	        SubscriptionPeriod $period,
    26	        float $discountAmount = 0,
    27	        ?string $discountReason = null,
    28	        ?string $notes = null,
    29	    ): Subscription {
    30	        // Prevent duplicates
    31	        $existing = Subscription::where('member_id', $member->id)
    32	            ->where('subscription_period_id', $period->id)
    33	            ->whereNull('deleted_at')
    34	            ->first();
    35	
    36	        if ($existing) {
    37	            throw new \DomainException("هذا العضو لديه اشتراك بالفعل لفترة {$period->display_name}");
    38	        }
    39	
    40	        $calculation = $this->calculator->calculate($member, $period, $discountAmount);
    41	
    42	        return DB::transaction(function () use ($member, $period, $calculation, $discountAmount, $discountReason, $notes) {
    43	            $dueDate = $period->grace_deadline ?? $period->start_date->addDays($period->grace_period_days);
    44	
    45	            $subscription = Subscription::create([
    46	                'member_id'              => $member->id,
    47	                'subscription_period_id' => $period->id,
    48	                'status'                 => $calculation['is_late'] ? SubscriptionStatus::OVERDUE : SubscriptionStatus::PENDING,
    49	                'base_amount'            => $calculation['base_amount'],
    50	                'dependents_amount'      => $calculation['dependents_amount'],
    51	                'total_amount'           => $calculation['total_amount'],
    52	                'discount_amount'        => $calculation['discount_amount'],
    53	                'late_fee_amount'        => $calculation['late_fee_amount'],
    54	                'net_amount'             => $calculation['net_amount'],
    55	                'paid_amount'            => 0,
    56	                'balance'                => $calculation['net_amount'],
    57	                'discount_reason'        => $discountReason,
    58	                'discount_approved_by'   => $discountAmount > 0 ? Auth::id() : null,
    59	                'due_date'               => $dueDate,
    60	                'is_late'                => $calculation['is_late'],
    61	                'late_applied_date'      => $calculation['is_late'] ? now() : null,
    62	                'dependents_breakdown'   => $calculation['dependents_breakdown'],
    63	                'notes'                  => $notes,
    64	                'created_by'             => Auth::id(),
    65	            ]);
    66	
    67	            return $subscription;
    68	        });
    69	    }
    70	
    71	    /**
    72	     * Record a payment against a subscription.
    73	     */
    74	    public function recordPayment(
    75	        Subscription $subscription,
    76	        float $amount,
    77	        string $paymentMethod = 'cash',
    78	        ?int $receiptId = null,
    79	        ?string $referenceNumber = null,
    80	        ?string $notes = null,
    81	    ): SubscriptionPayment {
    82	        if (!$subscription->status->isPayable()) {
    83	            throw new \DomainException('لا يمكن الدفع لهذا الاشتراك — الحالة: ' . $subscription->status->label());
    84	        }
    85	
    86	        if ($amount <= 0) {
    87	            throw new \DomainException('مبلغ الدفع يجب أن يكون أكبر من صفر.');
    88	        }
    89	
    90	        $remaining = $subscription->remaining_balance;
    91	        if ($amount > $remaining + 0.01) { // Small tolerance for rounding
    92	            throw new \DomainException("المبلغ المدفوع ({$amount}) أكبر من المتبقي ({$remaining}).");
    93	        }
    94	
    95	        return DB::transaction(function () use ($subscription, $amount, $paymentMethod, $receiptId, $referenceNumber, $notes) {
    96	            $payment = SubscriptionPayment::create([
    97	                'subscription_id'  => $subscription->id,
    98	                'receipt_id'       => $receiptId,
    99	                'amount'           => $amount,
   100	                'payment_date'     => now(),
   101	                'payment_method'   => $paymentMethod,
   102	                'reference_number' => $referenceNumber,
   103	                'notes'            => $notes,
   104	                'created_by'       => Auth::id(),
   105	            ]);
   106	
   107	            $subscription->recalculateBalance();
   108	
   109	            return $payment;
   110	        });
   111	    }
   112	
   113	    /**
   114	     * Reverse a payment.
   115	     */
   116	    public function reversePayment(SubscriptionPayment $payment, string $reason): void
   117	    {
   118	        DB::transaction(function () use ($payment, $reason) {
   119	            $payment->reverse(Auth::id(), $reason);
   120	        });
   121	    }
   122	
   123	    /**
   124	     * Waive a subscription entirely.
   125	     */
   126	    public function waiveSubscription(Subscription $subscription, string $reason): void
   127	    {
   128	        if ($subscription->status->isTerminal()) {
   129	            throw new \DomainException('لا يمكن إعفاء اشتراك في حالته الحالية: ' . $subscription->status->label());
   130	        }
   131	
   132	        $subscription->waive(Auth::id(), $reason);
   133	    }
   134	
   135	    /**
   136	     * Cancel a subscription.
   137	     */
   138	    public function cancelSubscription(Subscription $subscription): void
   139	    {
   140	        $subscription->cancel();
   141	    }
   142	
   143	    /**
   144	     * Recalculate a subscription (e.g., after dependent changes).
   145	     */
   146	    public function recalculate(Subscription $subscription): Subscription
   147	    {
   148	        if ($subscription->status->isTerminal()) {
   149	            throw new \DomainException('لا يمكن إعادة حساب اشتراك مكتمل.');
   150	        }
   151	
   152	        $member = $subscription->member;
   153	        $period = $subscription->subscriptionPeriod;
   154	        $calculation = $this->calculator->calculate($member, $period, $subscription->discount_amount);
   155	
   156	        $subscription->update([
   157	            'base_amount'          => $calculation['base_amount'],
   158	            'dependents_amount'    => $calculation['dependents_amount'],
   159	            'total_amount'         => $calculation['total_amount'],
   160	            'late_fee_amount'      => $calculation['late_fee_amount'],
   161	            'net_amount'           => $calculation['net_amount'],
   162	            'balance'              => max(0, $calculation['net_amount'] - $subscription->paid_amount),
   163	            'is_late'              => $calculation['is_late'],
   164	            'dependents_breakdown' => $calculation['dependents_breakdown'],
   165	            'updated_by'           => Auth::id(),
   166	        ]);
   167	
   168	        // Re-evaluate status based on new amounts
   169	        if ($subscription->paid_amount >= $subscription->net_amount && $subscription->net_amount > 0) {
   170	            $subscription->update(['status' => SubscriptionStatus::PAID]);
   171	        } elseif ($subscription->paid_amount > 0) {
   172	            $subscription->update(['status' => SubscriptionStatus::PARTIAL]);
   173	        } elseif ($calculation['is_late']) {
   174	            $subscription->update(['status' => SubscriptionStatus::OVERDUE]);
   175	        }
   176	
   177	        return $subscription->fresh();
   178	    }
   179	
   180	    /**
   181	     * Get subscription status summary for a member.
   182	     */
   183	    public function getMemberSubscriptionSummary(Member $member): array
   184	    {
   185	        $subscriptions = $member->subscriptions()
   186	            ->with('subscriptionPeriod')
   187	            ->orderByDesc('subscription_period_id')
   188	            ->get();
   189	
   190	        return [
   191	            'total_subscriptions' => $subscriptions->count(),
   192	            'paid_count'          => $subscriptions->where('status', SubscriptionStatus::PAID)->count(),
   193	            'overdue_count'       => $subscriptions->where('status', SubscriptionStatus::OVERDUE)->count(),
   194	            'pending_count'       => $subscriptions->where('status', SubscriptionStatus::PENDING)->count(),
   195	            'total_outstanding'   => $subscriptions->whereIn('status', SubscriptionStatus::unpaidStatuses())->sum('balance'),
   196	            'last_paid_year'      => $subscriptions->where('status', SubscriptionStatus::PAID)->first()?->subscriptionPeriod?->year,
   197	            'is_current'          => $subscriptions->where('status', SubscriptionStatus::PAID)
   198	                                        ->contains(fn ($s) => $s->subscriptionPeriod?->is_current),
   199	        ];
   200	    }
   201	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [376]: app/Services/Subscription/SubscriptionService.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [377/494]: bootstrap/providers.php
│ LANGUAGE: php | LINES: 6 | SIZE: 144 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	return [
     4	    App\Providers\AppServiceProvider::class,
     5	    App\Providers\SubscriptionServiceProvider::class,
     6	    // ... other providers
     7	];│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [377]: bootstrap/providers.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [378/494]: code.txt
│ LANGUAGE: plaintext | LINES: 2 | SIZE: 54 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	---
     2	
     3	## 14. MIGRATIONS (Laravel framework tables only)│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [378]: code.txt


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [379/494]: code_10.txt
│ LANGUAGE: plaintext | LINES: 0 | SIZE: 61 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	Now update the DatabaseSeeder to include the two new seeders:│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [379]: code_10.txt


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [380/494]: code_11.txt
│ LANGUAGE: plaintext | LINES: 2 | SIZE: 41 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	---
     2	
     3	## 23. NOTIFICATION TEMPLATES SEEDER│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [380]: code_11.txt


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [381/494]: code_12.txt
│ LANGUAGE: plaintext | LINES: 0 | SIZE: 35 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	Add this to the DatabaseSeeder too:│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [381]: code_12.txt


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [382/494]: code_13.txt
│ LANGUAGE: plaintext | LINES: 2 | SIZE: 29 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	---
     2	
     3	## 24. DEPENDENT SERVICE│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [382]: code_13.txt


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [383/494]: code_2.txt
│ LANGUAGE: plaintext | LINES: 2 | SIZE: 26 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	---
     2	
     3	## 15. LANGUAGE FILES│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [383]: code_2.txt


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [384/494]: code_3.txt
│ LANGUAGE: plaintext | LINES: 2 | SIZE: 35 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	---
     2	
     3	## 16. CUSTOM VALIDATION RULES│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [384]: code_3.txt


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [385/494]: code_4.txt
│ LANGUAGE: plaintext | LINES: 2 | SIZE: 31 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	---
     2	
     3	## 17. BLADE VIEWS FOR PDF│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [385]: code_4.txt


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [386/494]: code_5.txt
│ LANGUAGE: plaintext | LINES: 2 | SIZE: 48 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	---
     2	
     3	## 18. SETTINGS SERVICE (Singleton wrapper)│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [386]: code_5.txt


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [387/494]: code_6.txt
│ LANGUAGE: plaintext | LINES: 2 | SIZE: 25 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	---
     2	
     3	## 19. AUDIT SERVICE│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [387]: code_6.txt


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [388/494]: code_7.txt
│ LANGUAGE: plaintext | LINES: 2 | SIZE: 37 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	---
     2	
     3	## 20. STATUS TRANSITION SERVICE│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [388]: code_7.txt


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [389/494]: code_8.txt
│ LANGUAGE: plaintext | LINES: 2 | SIZE: 28 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	---
     2	
     3	## 21. WORKFLOW SERVICE│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [389]: code_8.txt


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [390/494]: code_9.txt
│ LANGUAGE: plaintext | LINES: 2 | SIZE: 37 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	---
     2	
     3	## 22. MEMBERSHIP STATUS SEEDERS│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [390]: code_9.txt


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [391/494]: codecoll.sh
│ LANGUAGE: bash | LINES: 595 | SIZE: 28878 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	#!/bin/zsh
     2	
     3	# ============================================================================
     4	#  CODEBASE COLLECTOR — "Give the AI EVERYTHING"
     5	#  Recursively grabs every code file, config, .env, Docker stuff, scripts,
     6	#  and packs it into one beautiful file with a full file map.
     7	# ============================================================================
     8	
     9	setopt NULL_GLOB 2>/dev/null
    10	
    11	# ── CONFIG ──────────────────────────────────────────────────────────────────
    12	OUTPUT_FILE="FULL_CODEBASE.txt"
    13	TARGET_DIR="${1:-.}"
    14	MAX_FILE_SIZE_KB=500
    15	TIMESTAMP=$(date "+%Y-%m-%d %H:%M:%S")
    16	
    17	# ── COLORS ──────────────────────────────────────────────────────────────────
    18	RED='\033[0;31m'
    19	GREEN='\033[0;32m'
    20	YELLOW='\033[1;33m'
    21	CYAN='\033[0;36m'
    22	BOLD='\033[1m'
    23	NC='\033[0m'
    24	
    25	# ── FILE EXTENSIONS TO INCLUDE ──────────────────────────────────────────────
    26	CODE_EXTENSIONS=(
    27	    py js ts jsx tsx go rs rb php java kt kts
    28	    scala c h cpp hpp cc cxx cs swift m mm
    29	    r R pl pm lua zig nim dart ex exs erl hrl
    30	    hs lhs ml mli fs fsx clj cljs cljc v sv
    31	    vhd vhdl sol move cairo
    32	    html htm css scss sass less styl
    33	    vue svelte astro mdx
    34	    sh bash zsh fish ps1 psm1 bat cmd
    35	    json jsonc json5 yaml yml toml ini cfg conf
    36	    xml xsl xslt plist hcl tf tfvars
    37	    md markdown txt rst adoc tex org
    38	    sql prisma graphql gql
    39	    gradle sbt cmake mk
    40	    gemspec podspec
    41	    env editorconfig prettierrc eslintrc babelrc browserslistrc
    42	    stylelintrc commitlintrc
    43	    lock map csv tsv
    44	    proto thrift avsc
    45	    ipynb
    46	    ejs hbs handlebars pug jade njk twig jinja j2
    47	)
    48	
    49	# ── EXACT FILENAMES TO ALWAYS INCLUDE ───────────────────────────────────────
    50	EXACT_FILENAMES=(
    51	    Dockerfile Dockerfile.dev Dockerfile.prod Dockerfile.staging
    52	    docker-compose.yml docker-compose.yaml docker-compose.dev.yml
    53	    docker-compose.prod.yml docker-compose.override.yml
    54	    .dockerignore .gitignore .gitattributes .gitmodules
    55	    .editorconfig
    56	    .prettierrc .prettierrc.json .prettierrc.yml .prettierrc.yaml
    57	    .prettierrc.js .prettierrc.cjs .prettierrc.toml .prettier.config.js
    58	    .eslintrc .eslintrc.js .eslintrc.cjs .eslintrc.json .eslintrc.yml .eslintrc.yaml
    59	    .babelrc .babelrc.json .stylelintrc .stylelintrc.json
    60	    .env .env.local .env.development .env.production .env.staging
    61	    .env.test .env.example .env.sample .env.template
    62	    .flake8 .pylintrc .rubocop.yml .ruby-version .node-version .nvmrc
    63	    .python-version .tool-versions
    64	    Makefile makefile GNUmakefile Rakefile Gemfile Gemfile.lock
    65	    Pipfile Pipfile.lock Cargo.toml Cargo.lock go.mod go.sum
    66	    package.json package-lock.json yarn.lock pnpm-lock.yaml bun.lockb
    67	    tsconfig.json tsconfig.base.json tsconfig.build.json jsconfig.json
    68	    webpack.config.js webpack.config.ts vite.config.js vite.config.ts
    69	    rollup.config.js rollup.config.ts esbuild.config.js
    70	    next.config.js next.config.mjs next.config.ts
    71	    nuxt.config.js nuxt.config.ts svelte.config.js astro.config.mjs
    72	    tailwind.config.js tailwind.config.ts postcss.config.js postcss.config.cjs
    73	    jest.config.js jest.config.ts vitest.config.ts vitest.config.js
    74	    pytest.ini setup.py setup.cfg pyproject.toml
    75	    requirements.txt requirements-dev.txt requirements.in constraints.txt
    76	    tox.ini poetry.lock composer.json composer.lock
    77	    build.gradle build.gradle.kts settings.gradle settings.gradle.kts
    78	    pom.xml CMakeLists.txt meson.build
    79	    Justfile justfile Taskfile.yml Taskfile.yaml Earthfile Tiltfile Brewfile
    80	    Procfile Vagrantfile Jenkinsfile
    81	    cloudbuild.yaml serverless.yml serverless.yaml serverless.ts
    82	    fly.toml render.yaml app.yaml app.json vercel.json netlify.toml
    83	    firebase.json .firebaserc angular.json nx.json project.json workspace.json
    84	    lerna.json turbo.json .swcrc biome.json deno.json deno.jsonc import_map.json
    85	    renovate.json .releaserc .releaserc.json .releaserc.yml
    86	    CODEOWNERS LICENSE LICENSE.md LICENSE.txt
    87	    CHANGELOG.md CONTRIBUTING.md README.md README.rst TODO.md
    88	)
    89	
    90	# ── DIRECTORIES TO SKIP ─────────────────────────────────────────────────────
    91	SKIP_DIRS=(
    92	    node_modules .git .svn .hg
    93	    __pycache__ .pytest_cache .mypy_cache .ruff_cache .tox .nox
    94	    venv .venv .env_dir virtualenv .virtualenv
    95	    .idea .vscode .vs
    96	    dist build out target bin obj
    97	    .next .nuxt .output .svelte-kit .astro .vercel .netlify .serverless
    98	    .terraform .gradle .maven .cargo
    99	    vendor Pods DerivedData
   100	    .sass-cache .parcel-cache .turbo .cache
   101	    coverage .nyc_output htmlcov
   102	    .eggs .bundle
   103	    tmp temp logs
   104	    __MACOSX
   105	)
   106	
   107	# ── CHECK IF PATH IS INSIDE A SKIPPED DIR ──────────────────────────────────
   108	is_in_skip_dir() {
   109	    local filepath="$1"
   110	    for dir in "${SKIP_DIRS[@]}"; do
   111	        if [[ "$filepath" == *"/${dir}/"* ]] || [[ "$filepath" == *"/${dir}" ]]; then
   112	            return 0
   113	        fi
   114	        # Also catch ./dir/ at the start
   115	        if [[ "$filepath" == "${dir}/"* ]] || [[ "$filepath" == "./${dir}/"* ]]; then
   116	            return 0
   117	        fi
   118	    done
   119	    return 1
   120	}
   121	
   122	# ── CHECK IF FILE SHOULD BE INCLUDED ───────────────────────────────────────
   123	should_include() {
   124	    local filepath="$1"
   125	    local filename="${filepath:t}"   # zsh way to get basename
   126	    local extension="${filename:e}"  # zsh way to get extension
   127	
   128	    # Check exact filename matches
   129	    for exact in "${EXACT_FILENAMES[@]}"; do
   130	        if [[ "$filename" == "$exact" ]]; then
   131	            return 0
   132	        fi
   133	    done
   134	
   135	    # Catch .env.anything
   136	    if [[ "$filename" == .env* ]]; then
   137	        return 0
   138	    fi
   139	
   140	    # Catch Dockerfile.anything
   141	    if [[ "$filename" == Dockerfile* ]] || [[ "$filename" == dockerfile* ]]; then
   142	        return 0
   143	    fi
   144	
   145	    # Catch docker-compose*.anything
   146	    if [[ "$filename" == docker-compose* ]]; then
   147	        return 0
   148	    fi
   149	
   150	    # Catch GitHub Actions / CI workflows
   151	    if [[ "$filepath" == *".github/"* ]] && [[ "$extension" == "yml" || "$extension" == "yaml" ]]; then
   152	        return 0
   153	    fi
   154	
   155	    # Check extension match
   156	    if [[ -n "$extension" ]]; then
   157	        for ext in "${CODE_EXTENSIONS[@]}"; do
   158	            if [[ "$extension" == "$ext" ]]; then
   159	                return 0
   160	            fi
   161	        done
   162	    fi
   163	
   164	    return 1
   165	}
   166	
   167	# ── CHECK IF FILE IS BINARY ────────────────────────────────────────────────
   168	is_binary() {
   169	    local filepath="$1"
   170	    local mime
   171	    mime=$(file --mime-encoding "$filepath" 2>/dev/null)
   172	    if [[ "$mime" == *"binary"* ]]; then
   173	        return 0
   174	    fi
   175	    return 1
   176	}
   177	
   178	# ── DETECT LANGUAGE FOR SYNTAX HINT ────────────────────────────────────────
   179	get_language_hint() {
   180	    local filename="${1:t}"
   181	    local ext="${filename:e}"
   182	
   183	    # Exact name matches first
   184	    case "$filename" in
   185	        Dockerfile*)         echo "dockerfile"; return ;;
   186	        Makefile|makefile|GNUmakefile) echo "makefile"; return ;;
   187	        Jenkinsfile)         echo "groovy"; return ;;
   188	        Vagrantfile|Rakefile|Gemfile) echo "ruby"; return ;;
   189	        *.env*)              echo "dotenv"; return ;;
   190	    esac
   191	
   192	    case "$ext" in
   193	        py)         echo "python" ;;
   194	        js)         echo "javascript" ;;
   195	        ts)         echo "typescript" ;;
   196	        jsx)        echo "jsx" ;;
   197	        tsx)        echo "tsx" ;;
   198	        go)         echo "go" ;;
   199	        rs)         echo "rust" ;;
   200	        rb)         echo "ruby" ;;
   201	        php)        echo "php" ;;
   202	        java)       echo "java" ;;
   203	        kt|kts)     echo "kotlin" ;;
   204	        scala)      echo "scala" ;;
   205	        c|h)        echo "c" ;;
   206	        cpp|hpp|cc) echo "cpp" ;;
   207	        cs)         echo "csharp" ;;
   208	        swift)      echo "swift" ;;
   209	        html|htm)   echo "html" ;;
   210	        css)        echo "css" ;;
   211	        scss)       echo "scss" ;;
   212	        json|jsonc) echo "json" ;;
   213	        yaml|yml)   echo "yaml" ;;
   214	        toml)       echo "toml" ;;
   215	        xml)        echo "xml" ;;
   216	        sql)        echo "sql" ;;
   217	        sh|bash)    echo "bash" ;;
   218	        zsh)        echo "zsh" ;;
   219	        md)         echo "markdown" ;;
   220	        graphql|gql) echo "graphql" ;;
   221	        tf|hcl)     echo "hcl" ;;
   222	        lua)        echo "lua" ;;
   223	        dart)       echo "dart" ;;
   224	        ex|exs)     echo "elixir" ;;
   225	        vue)        echo "vue" ;;
   226	        svelte)     echo "svelte" ;;
   227	        prisma)     echo "prisma" ;;
   228	        sol)        echo "solidity" ;;
   229	        ini|cfg|conf) echo "ini" ;;
   230	        proto)      echo "protobuf" ;;
   231	        r|R)        echo "r" ;;
   232	        *)          echo "plaintext" ;;
   233	    esac
   234	}
   235	
   236	# ── MAIN ────────────────────────────────────────────────────────────────────
   237	
   238	echo ""
   239	echo "${BOLD}${CYAN}╔══════════════════════════════════════════════════════════════╗${NC}"
   240	echo "${BOLD}${CYAN}║          🔥  CODEBASE COLLECTOR — GRAB EVERYTHING  🔥       ║${NC}"
   241	echo "${BOLD}${CYAN}╚══════════════════════════════════════════════════════════════╝${NC}"
   242	echo ""
   243	
   244	# Resolve to absolute path
   245	ABS_TARGET=$(cd "$TARGET_DIR" && pwd)
   246	
   247	echo "${YELLOW}Target directory:${NC} $ABS_TARGET"
   248	echo "${YELLOW}Output file:${NC}      $OUTPUT_FILE"
   249	echo "${YELLOW}Max file size:${NC}    ${MAX_FILE_SIZE_KB}KB"
   250	echo ""
   251	
   252	# ── PHASE 1: COLLECT ALL FILE PATHS ────────────────────────────────────────
   253	echo "${CYAN}[1/4]${NC} 🔍 Scanning for files..."
   254	
   255	collected_files=()
   256	skipped_binary=()
   257	skipped_size=()
   258	skipped_self=0
   259	total_scanned=0
   260	
   261	# Simple and robust: just find ALL regular files, filter in the loop
   262	while IFS= read -r file; do
   263	    total_scanned=$((total_scanned + 1))
   264	
   265	    # Skip our own output file
   266	    local_name="${file:t}"
   267	    if [[ "$local_name" == "$OUTPUT_FILE" ]] || [[ "$local_name" == "CollectCode.sh" ]]; then
   268	        skipped_self=$((skipped_self + 1))
   269	        continue
   270	    fi
   271	
   272	    # Make a relative path for checking skip dirs
   273	    relpath="${file#$ABS_TARGET/}"
   274	
   275	    # Check if in a skipped directory
   276	    if is_in_skip_dir "$relpath"; then
   277	        continue
   278	    fi
   279	
   280	    # Check if we care about this file
   281	    if should_include "$file"; then
   282	        # Check file size
   283	        local_size=$(stat -f%z "$file" 2>/dev/null || wc -c < "$file" 2>/dev/null | tr -d ' ')
   284	        local_size_kb=$((local_size / 1024))
   285	
   286	        if [[ $local_size_kb -gt $MAX_FILE_SIZE_KB ]]; then
   287	            skipped_size+=("$relpath (${local_size_kb}KB)")
   288	            continue
   289	        fi
   290	
   291	        # Check if binary
   292	        if is_binary "$file"; then
   293	            skipped_binary+=("$relpath")
   294	            continue
   295	        fi
   296	
   297	        collected_files+=("$file")
   298	    fi
   299	done < <(find "$ABS_TARGET" -type f 2>/dev/null | sort)
   300	
   301	echo "${GREEN}   ✓ Scanned ${total_scanned} total files${NC}"
   302	echo "${GREEN}   ✓ Found ${#collected_files[@]} files to include${NC}"
   303	[[ ${#skipped_binary[@]} -gt 0 ]] && echo "${YELLOW}   ⊘ Skipped ${#skipped_binary[@]} binary files${NC}"
   304	[[ ${#skipped_size[@]} -gt 0 ]] && echo "${YELLOW}   ⊘ Skipped ${#skipped_size[@]} files over ${MAX_FILE_SIZE_KB}KB${NC}"
   305	
   306	if [[ ${#collected_files[@]} -eq 0 ]]; then
   307	    echo ""
   308	    echo "${RED}   ✗ No files found!${NC}"
   309	    echo ""
   310	    echo "${YELLOW}   DEBUG: Listing first 20 files found by find:${NC}"
   311	    find "$ABS_TARGET" -type f 2>/dev/null | head -20 | while read f; do
   312	        relp="${f#$ABS_TARGET/}"
   313	        echo "     $relp"
   314	    done
   315	    echo ""
   316	    echo "${YELLOW}   If you see files above, the extension/name filter isn't matching them.${NC}"
   317	    echo "${YELLOW}   Check CODE_EXTENSIONS and EXACT_FILENAMES arrays in the script.${NC}"
   318	    exit 1
   319	fi
   320	
   321	# ── PHASE 2: ORGANIZE + STATS ──────────────────────────────────────────────
   322	echo "${CYAN}[2/4]${NC} 📂 Building file map..."
   323	
   324	total_lines=0
   325	total_bytes=0
   326	typeset -A ext_count
   327	
   328	for file in "${collected_files[@]}"; do
   329	    lines=$(wc -l < "$file" 2>/dev/null | tr -d ' ')
   330	    bytes=$(stat -f%z "$file" 2>/dev/null || wc -c < "$file" 2>/dev/null | tr -d ' ')
   331	    total_lines=$((total_lines + lines))
   332	    total_bytes=$((total_bytes + bytes))
   333	
   334	    fname="${file:t}"
   335	    ext="${fname:e}"
   336	    # For dotfiles without extension, use filename
   337	    if [[ -z "$ext" ]]; then
   338	        ext="$fname"
   339	    fi
   340	    ext_count[$ext]=$(( ${ext_count[$ext]:-0} + 1 ))
   341	done
   342	
   343	# Human-readable size
   344	if [[ $total_bytes -gt 1048576 ]]; then
   345	    total_size="$(( total_bytes / 1048576 )).$(( (total_bytes % 1048576) * 10 / 1048576 ))MB"
   346	elif [[ $total_bytes -gt 1024 ]]; then
   347	    total_size="$(( total_bytes / 1024 ))KB"
   348	else
   349	    total_size="${total_bytes}B"
   350	fi
   351	
   352	echo "${GREEN}   ✓ ${#collected_files[@]} files, ${total_lines} lines, ${total_size}${NC}"
   353	
   354	echo "${CYAN}[3/4]${NC} 📊 Sorting file types..."
   355	
   356	# Build sorted type breakdown string
   357	type_breakdown=""
   358	for ext in "${(@k)ext_count}"; do
   359	    type_breakdown+="${ext_count[$ext]} .${ext}\n"
   360	done
   361	type_breakdown=$(echo "$type_breakdown" | sort -rn)
   362	
   363	echo "${GREEN}   ✓ Found ${#ext_count[@]} different file types${NC}"
   364	
   365	# ── PHASE 3: WRITE OUTPUT ──────────────────────────────────────────────────
   366	echo "${CYAN}[4/4]${NC} ✍️  Writing ${OUTPUT_FILE}..."
   367	
   368	{
   369	    cat << 'HEADER_ART'
   370	################################################################################
   371	#                                                                              #
   372	#   ██████╗ ██████╗ ██████╗ ███████╗██████╗  █████╗ ███████╗███████╗          #
   373	#  ██╔════╝██╔═══██╗██╔══██╗██╔════╝██╔══██╗██╔══██╗██╔════╝██╔════╝          #
   374	#  ██║     ██║   ██║██║  ██║█████╗  ██████╔╝███████║███████╗█████╗            #
   375	#  ██║     ██║   ██║██║  ██║██╔══╝  ██╔══██╗██╔══██║╚════██║██╔══╝            #
   376	#  ╚██████╗╚██████╔╝██████╔╝███████╗██████╔╝██║  ██║███████║███████╗          #
   377	#   ╚═════╝ ╚═════╝ ╚═════╝ ╚══════╝╚═════╝ ╚═╝  ╚═╝╚══════╝╚══════╝          #
   378	#                                                                              #
   379	#          COMPLETE CODEBASE DUMP — EVERY FILE, EVERY LINE                     #
   380	#                                                                              #
   381	################################################################################
   382	HEADER_ART
   383	
   384	    echo ""
   385	    echo "=============================================================================="
   386	    echo " PROJECT CODEBASE — FULL SOURCE DUMP"
   387	    echo "=============================================================================="
   388	    echo ""
   389	    echo " Generated:       $TIMESTAMP"
   390	    echo " Source Dir:       $ABS_TARGET"
   391	    echo " Total Files:      ${#collected_files[@]}"
   392	    echo " Total Lines:      ${total_lines}"
   393	    echo " Total Size:       ${total_size}"
   394	    echo ""
   395	    echo " THIS FILE CONTAINS THE COMPLETE CODEBASE INCLUDING:"
   396	    echo "   • All source code files (every language found)"
   397	    echo "   • All configuration files (json, yaml, toml, xml, ini, etc.)"
   398	    echo "   • All environment files (.env, .env.local, .env.production, etc.)"
   399	    echo "   • All Docker files (Dockerfile, docker-compose.yml, .dockerignore)"
   400	    echo "   • All CI/CD configs (Jenkinsfile, GitHub Actions, etc.)"
   401	    echo "   • All build configs (webpack, vite, tsconfig, Makefile, etc.)"
   402	    echo "   • All package manifests (package.json, Cargo.toml, go.mod, etc.)"
   403	    echo "   • All lock files (package-lock.json, yarn.lock, etc.)"
   404	    echo "   • All documentation (README, CHANGELOG, LICENSE, etc.)"
   405	    echo "   • All scripts (shell, python, etc.)"
   406	    echo ""
   407	    echo " STRUCTURE OF THIS FILE:"
   408	    echo "   1. DIRECTORY TREE"
   409	    echo "   2. FILE MAP (indexed table of every file)"
   410	    echo "   3. FILE TYPE BREAKDOWN (stats by extension)"
   411	    echo "   4. SKIPPED FILES (binary/oversized — for transparency)"
   412	    echo "   5. COMPLETE FILE CONTENTS (every file printed in full)"
   413	    echo ""
   414	    echo "=============================================================================="
   415	    echo ""
   416	    echo ""
   417	
   418	    # ── SECTION 1: DIRECTORY TREE ───────────────────────────────────────────
   419	    echo "╔══════════════════════════════════════════════════════════════════════════════╗"
   420	    echo "║  SECTION 1: DIRECTORY TREE                                                  ║"
   421	    echo "╚══════════════════════════════════════════════════════════════════════════════╝"
   422	    echo ""
   423	
   424	    if command -v tree &>/dev/null; then
   425	        tree_ignore=$(printf "%s|" "${SKIP_DIRS[@]}")
   426	        tree_ignore="${tree_ignore%|}"
   427	        tree -a -I "$tree_ignore" --charset=utf-8 "$ABS_TARGET" 2>/dev/null || echo "(tree output failed)"
   428	    else
   429	        echo "(For a visual tree: brew install tree)"
   430	        echo ""
   431	        echo "Collected file paths:"
   432	        for file in "${collected_files[@]}"; do
   433	            relpath="${file#$ABS_TARGET/}"
   434	            echo "  $relpath"
   435	        done
   436	    fi
   437	
   438	    echo ""
   439	    echo ""
   440	
   441	    # ── SECTION 2: FILE MAP ─────────────────────────────────────────────────
   442	    echo "╔══════════════════════════════════════════════════════════════════════════════╗"
   443	    echo "║  SECTION 2: FILE MAP — INDEXED LIST OF ALL ${#collected_files[@]} FILES"
   444	    echo "╚══════════════════════════════════════════════════════════════════════════════╝"
   445	    echo ""
   446	    echo " Each file below appears in SECTION 5 with full contents."
   447	    echo " Use [###] index to jump to any file."
   448	    echo ""
   449	    printf " %-6s  %-7s  %-8s  %s\n" "INDEX" "LINES" "SIZE" "FILE PATH"
   450	    printf " %-6s  %-7s  %-8s  %s\n" "-----" "-----" "------" "----------------------------------------------"
   451	
   452	    idx=1
   453	    for file in "${collected_files[@]}"; do
   454	        relpath="${file#$ABS_TARGET/}"
   455	        lines=$(wc -l < "$file" 2>/dev/null | tr -d ' ')
   456	        bytes=$(stat -f%z "$file" 2>/dev/null || wc -c < "$file" 2>/dev/null | tr -d ' ')
   457	
   458	        if [[ $bytes -gt 1024 ]]; then
   459	            size="$(( bytes / 1024 ))KB"
   460	        else
   461	            size="${bytes}B"
   462	        fi
   463	
   464	        printf " [%03d]  %-7s  %-8s  %s\n" "$idx" "$lines" "$size" "$relpath"
   465	        idx=$((idx + 1))
   466	    done
   467	
   468	    echo ""
   469	    echo ""
   470	
   471	    # ── SECTION 3: FILE TYPE BREAKDOWN ──────────────────────────────────────
   472	    echo "╔══════════════════════════════════════════════════════════════════════════════╗"
   473	    echo "║  SECTION 3: FILE TYPE BREAKDOWN                                             ║"
   474	    echo "╚══════════════════════════════════════════════════════════════════════════════╝"
   475	    echo ""
   476	    printf " %-25s  %s\n" "EXTENSION/TYPE" "COUNT"
   477	    printf " %-25s  %s\n" "───────────────────────" "─────"
   478	
   479	    echo "$type_breakdown" | while read count ext; do
   480	        [[ -z "$count" ]] && continue
   481	        printf " %-25s  %s\n" "$ext" "$count"
   482	    done
   483	
   484	    echo ""
   485	    echo ""
   486	
   487	    # ── SECTION 4: SKIPPED FILES ────────────────────────────────────────────
   488	    if [[ ${#skipped_binary[@]} -gt 0 || ${#skipped_size[@]} -gt 0 ]]; then
   489	        echo "╔══════════════════════════════════════════════════════════════════════════════╗"
   490	        echo "║  SECTION 4: SKIPPED FILES (listed for completeness)                         ║"
   491	        echo "╚══════════════════════════════════════════════════════════════════════════════╝"
   492	        echo ""
   493	
   494	        if [[ ${#skipped_binary[@]} -gt 0 ]]; then
   495	            echo " Binary files (not text):"
   496	            for f in "${skipped_binary[@]}"; do
   497	                echo "   ⊘ $f"
   498	            done
   499	            echo ""
   500	        fi
   501	
   502	        if [[ ${#skipped_size[@]} -gt 0 ]]; then
   503	            echo " Oversized files (>${MAX_FILE_SIZE_KB}KB):"
   504	            for f in "${skipped_size[@]}"; do
   505	                echo "   ⊘ $f"
   506	            done
   507	            echo ""
   508	        fi
   509	        echo ""
   510	    fi
   511	
   512	    # ── SECTION 5: FILE CONTENTS ────────────────────────────────────────────
   513	    echo "╔══════════════════════════════════════════════════════════════════════════════╗"
   514	    echo "║  SECTION 5: COMPLETE FILE CONTENTS                                          ║"
   515	    echo "║                                                                              ║"
   516	    echo "║  Every file printed in full with:                                            ║"
   517	    echo "║    • Clear start/end markers                                                 ║"
   518	    echo "║    • File path, size, line count in the header                               ║"
   519	    echo "║    • Language hint for syntax context                                        ║"
   520	    echo "╚══════════════════════════════════════════════════════════════════════════════╝"
   521	    echo ""
   522	    echo ""
   523	
   524	    idx=1
   525	    for file in "${collected_files[@]}"; do
   526	        relpath="${file#$ABS_TARGET/}"
   527	        lines=$(wc -l < "$file" 2>/dev/null | tr -d ' ')
   528	        bytes=$(stat -f%z "$file" 2>/dev/null || wc -c < "$file" 2>/dev/null | tr -d ' ')
   529	        lang=$(get_language_hint "$file")
   530	
   531	        echo "┌──────────────────────────────────────────────────────────────────────────────"
   532	        echo "│ 📄 FILE [$(printf '%03d' $idx)/${#collected_files[@]}]: $relpath"
   533	        echo "│ LANGUAGE: $lang | LINES: $lines | SIZE: ${bytes} bytes"
   534	        echo "├──────────────────────────────────────────────────────────────────────────────"
   535	        echo "│"
   536	
   537	        if [[ -s "$file" ]]; then
   538	            cat -n "$file" 2>/dev/null || echo "│ [ERROR: Could not read file]"
   539	        else
   540	            echo "│ [EMPTY FILE]"
   541	        fi
   542	
   543	        echo "│"
   544	        echo "└──────────────────────────────────────────────────────────────────────────────"
   545	        echo "   ✅ END OF [$(printf '%03d' $idx)]: $relpath"
   546	        echo ""
   547	        echo ""
   548	
   549	        # Progress to stderr
   550	        if (( idx % 25 == 0 )); then
   551	            echo "   ... ${idx}/${#collected_files[@]} files written ..." >&2
   552	        fi
   553	
   554	        idx=$((idx + 1))
   555	    done
   556	
   557	    # ── FOOTER ──────────────────────────────────────────────────────────────
   558	    echo ""
   559	    echo "################################################################################"
   560	    echo "#                                                                              #"
   561	    echo "#                     ✅ END OF COMPLETE CODEBASE DUMP                         #"
   562	    echo "#                                                                              #"
   563	    printf "#  Total Files:  %-58s #\n" "${#collected_files[@]}"
   564	    printf "#  Total Lines:  %-58s #\n" "${total_lines}"
   565	    printf "#  Total Size:   %-58s #\n" "${total_size}"
   566	    printf "#  Generated:    %-58s #\n" "${TIMESTAMP}"
   567	    echo "#                                                                              #"
   568	    echo "#  This file contains EVERYTHING: source code, configs, env vars, Docker,      #"
   569	    echo "#  CI/CD, build tools, package manifests, docs — the complete picture.          #"
   570	    echo "#                                                                              #"
   571	    echo "################################################################################"
   572	
   573	} > "$OUTPUT_FILE"
   574	
   575	# ── DONE ────────────────────────────────────────────────────────────────────
   576	output_bytes=$(stat -f%z "$OUTPUT_FILE" 2>/dev/null || wc -c < "$OUTPUT_FILE" | tr -d ' ')
   577	if [[ $output_bytes -gt 1048576 ]]; then
   578	    output_human="$(( output_bytes / 1048576 ))MB"
   579	elif [[ $output_bytes -gt 1024 ]]; then
   580	    output_human="$(( output_bytes / 1024 ))KB"
   581	else
   582	    output_human="${output_bytes}B"
   583	fi
   584	
   585	echo ""
   586	echo "${BOLD}${GREEN}╔══════════════════════════════════════════════════════════════╗${NC}"
   587	echo "${BOLD}${GREEN}║                    ✅  DONE — ALL COLLECTED                  ║${NC}"
   588	echo "${BOLD}${GREEN}╚══════════════════════════════════════════════════════════════╝${NC}"
   589	echo ""
   590	echo "  ${BOLD}Output:${NC}       $OUTPUT_FILE"
   591	echo "  ${BOLD}Output size:${NC}  $output_human"
   592	echo "  ${BOLD}Files:${NC}        ${#collected_files[@]}"
   593	echo "  ${BOLD}Total lines:${NC}  ${total_lines}"
   594	echo ""
   595	echo "  ${YELLOW}Feed this to any AI and it'll know your ENTIRE codebase.${NC}"
   596	echo ""│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [391]: codecoll.sh


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [392/494]: composer.json
│ LANGUAGE: json | LINES: 78 | SIZE: 2539 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	{
     2	    "name": "al-arcade/club-management",
     3	    "type": "project",
     4	    "description": "AL-ARCADE Club Management ERP",
     5	    "keywords": ["laravel", "filament", "club", "erp"],
     6	    "license": "proprietary",
     7	    "require": {
     8	        "php": "^8.3",
     9	        "laravel/framework": "^11.0",
    10	        "filament/filament": "^3.2",
    11	        "filament/spatie-laravel-media-library-plugin": "^3.2",
    12	        "filament/spatie-laravel-settings-plugin": "^3.2",
    13	        "spatie/laravel-permission": "^6.4",
    14	        "bezhansalleh/filament-shield": "^3.2",
    15	        "spatie/laravel-activitylog": "^4.8",
    16	        "spatie/laravel-medialibrary": "^11.4",
    17	        "barryvdh/laravel-dompdf": "^2.1",
    18	        "maatwebsite/excel": "^3.1",
    19	        "spatie/laravel-backup": "^8.6",
    20	        "mhmiton/laravel-modules-livewire": "^2.3",
    21	        "malzariey/filament-daterangepicker-filter": "^3.0",
    22	        "awcodes/filament-table-repeater": "^3.0",
    23	        "filament/notifications": "^3.2"
    24	    },
    25	    "require-dev": {
    26	        "fakerphp/faker": "^1.23",
    27	        "laravel/pint": "^1.14",
    28	        "laravel/sail": "^1.29",
    29	        "mockery/mockery": "^1.6",
    30	        "nunomaduro/collision": "^8.1",
    31	        "larastan/larastan": "^2.9",
    32	        "pestphp/pest": "^2.34",
    33	        "pestphp/pest-plugin-laravel": "^2.4"
    34	    },
    35	    "autoload": {
    36	        "psr-4": {
    37	            "App\\": "app/",
    38	            "Database\\Factories\\": "database/factories/",
    39	            "Database\\Seeders\\": "database/seeders/"
    40	        }
    41	    },
    42	    "autoload-dev": {
    43	        "psr-4": {
    44	            "Tests\\": "tests/"
    45	        }
    46	    },
    47	    "scripts": {
    48	        "post-autoload-dump": [
    49	            "Illuminate\\Foundation\\ComposerScripts::postAutoloadDump",
    50	            "@php artisan package:discover --ansi",
    51	            "@php artisan filament:upgrade"
    52	        ],
    53	        "post-update-cmd": [
    54	            "@php artisan vendor:publish --tag=laravel-assets --ansi --force"
    55	        ],
    56	        "post-root-package-install": [
    57	            "@php -r \"file_exists('.env') || copy('.env.example', '.env');\""
    58	        ],
    59	        "post-create-project-cmd": [
    60	            "@php artisan key:generate --ansi"
    61	        ]
    62	    },
    63	    "extra": {
    64	        "laravel": {
    65	            "dont-discover": []
    66	        }
    67	    },
    68	    "config": {
    69	        "optimize-autoloader": true,
    70	        "preferred-install": "dist",
    71	        "sort-packages": true,
    72	        "allow-plugins": {
    73	            "pestphp/pest-plugin": true,
    74	            "php-http/discovery": true
    75	        }
    76	    },
    77	    "minimum-stability": "stable",
    78	    "prefer-stable": true
    79	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [392]: composer.json


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [393/494]: config/club.php
│ LANGUAGE: php | LINES: 73 | SIZE: 2668 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	return [
     4	
     5	    /*
     6	    |--------------------------------------------------------------------------
     7	    | Club Identity
     8	    |--------------------------------------------------------------------------
     9	    */
    10	    'name_ar' => env('CLUB_NAME_AR', 'نادي الآركيد'),
    11	    'name_en' => env('CLUB_NAME_EN', 'AL-ARCADE Club'),
    12	
    13	    /*
    14	    |--------------------------------------------------------------------------
    15	    | Localization & Currency
    16	    |--------------------------------------------------------------------------
    17	    */
    18	    'currency' => env('CLUB_CURRENCY', 'EGP'),
    19	    'currency_name_ar' => env('CLUB_CURRENCY_NAME_AR', 'جنيه'),
    20	    'currency_subunit_ar' => env('CLUB_CURRENCY_SUBUNIT_AR', 'قرش'),
    21	    'default_language' => env('CLUB_DEFAULT_LANGUAGE', 'ar'),
    22	    'date_format' => env('CLUB_DATE_FORMAT', 'd/m/Y'),
    23	
    24	    /*
    25	    |--------------------------------------------------------------------------
    26	    | Fiscal Year
    27	    |--------------------------------------------------------------------------
    28	    */
    29	    'fiscal_year_start_month' => (int) env('CLUB_FISCAL_YEAR_START_MONTH', 1),
    30	    'fiscal_year_start_day' => (int) env('CLUB_FISCAL_YEAR_START_DAY', 1),
    31	
    32	    /*
    33	    |--------------------------------------------------------------------------
    34	    | Member Constraints
    35	    |--------------------------------------------------------------------------
    36	    */
    37	    'max_dependents_per_member' => (int) env('CLUB_MAX_DEPENDENTS_PER_MEMBER', 10),
    38	    'member_photo_max_kb' => (int) env('CLUB_MEMBER_PHOTO_MAX_KB', 2048),
    39	    'document_max_kb' => (int) env('CLUB_DOCUMENT_MAX_KB', 5120),
    40	
    41	    /*
    42	    |--------------------------------------------------------------------------
    43	    | Upload Paths
    44	    |--------------------------------------------------------------------------
    45	    */
    46	    'uploads' => [
    47	        'member_photos' => 'members/photos',
    48	        'documents' => 'members/documents',
    49	        'card_photos' => 'cards/photos',
    50	        'imports' => 'imports',
    51	        'exports' => 'exports',
    52	        'backups' => 'backups',
    53	        'temp' => 'temp',
    54	    ],
    55	
    56	    /*
    57	    |--------------------------------------------------------------------------
    58	    | Numbering
    59	    |--------------------------------------------------------------------------
    60	    */
    61	    'numbering' => [
    62	        'membership_start' => 1001,
    63	        'receipt_pad_length' => 6,
    64	        'card_pad_length' => 8,
    65	    ],
    66	
    67	    /*
    68	    |--------------------------------------------------------------------------
    69	    | Archival
    70	    |--------------------------------------------------------------------------
    71	    */
    72	    'audit_retention_years' => 5,
    73	    'backup_retention_count' => 30,
    74	];│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [393]: config/club.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [394/494]: database/factories/CardFactory.php
│ LANGUAGE: php | LINES: 28 | SIZE: 776 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace Database\Factories;
     4	
     5	use App\Models\Card;
     6	use App\Models\Member;
     7	use Illuminate\Database\Eloquent\Factories\Factory;
     8	
     9	class CardFactory extends Factory
    10	{
    11	    protected $model = Card::class;
    12	
    13	    public function definition(): array
    14	    {
    15	        $year = (int) now()->format('Y');
    16	
    17	        return [
    18	            'card_number' => 'CRD-' . str_pad((string) fake()->unique()->numberBetween(1, 99999), 6, '0', STR_PAD_LEFT),
    19	            'member_id' => Member::factory(),
    20	            'card_type' => 'member',
    21	            'status' => 'active',
    22	            'valid_for_year' => $year,
    23	            'issue_date' => now()->startOfYear(),
    24	            'expiry_date' => now()->endOfYear(),
    25	            'print_count' => 0,
    26	            'is_archived' => false,
    27	        ];
    28	    }
    29	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [394]: database/factories/CardFactory.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [395/494]: database/factories/DependentFactory.php
│ LANGUAGE: php | LINES: 65 | SIZE: 2465 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace Database\Factories;
     4	
     5	use App\Models\Dependent;
     6	use App\Models\Member;
     7	use Illuminate\Database\Eloquent\Factories\Factory;
     8	
     9	class DependentFactory extends Factory
    10	{
    11	    protected $model = Dependent::class;
    12	
    13	    public function definition(): array
    14	    {
    15	        $relation = fake()->randomElement(['spouse', 'child', 'child', 'child']);
    16	        $gender = match ($relation) {
    17	            'spouse' => fake()->randomElement(['male', 'female']),
    18	            'child' => fake()->randomElement(['male', 'female']),
    19	        };
    20	
    21	        $birthDate = match ($relation) {
    22	            'spouse' => fake()->dateTimeBetween('-60 years', '-18 years'),
    23	            'child' => fake()->dateTimeBetween('-25 years', '-1 year'),
    24	        };
    25	
    26	        $arabicFirstNames = $gender === 'male'
    27	            ? ['أحمد', 'محمد', 'عبد الله', 'يوسف', 'خالد', 'عمر']
    28	            : ['فاطمة', 'مريم', 'نور', 'سارة', 'هدى', 'أمينة'];
    29	
    30	        $centuryDigit = $birthDate->format('Y') < 2000 ? '2' : '3';
    31	        $dobPart = $birthDate->format('ymd');
    32	        $govCode = str_pad((string) fake()->numberBetween(1, 27), 2, '0', STR_PAD_LEFT);
    33	        $seqPart = str_pad((string) fake()->numberBetween(1, 9999), 4, '0', STR_PAD_LEFT);
    34	        $genderDigit = $gender === 'male' ? fake()->randomElement([1, 3, 5, 7, 9]) : fake()->randomElement([2, 4, 6, 8]);
    35	        $nationalId = $centuryDigit . $dobPart . $govCode . $seqPart . $genderDigit;
    36	
    37	        return [
    38	            'member_id' => Member::factory(),
    39	            'name_ar' => fake()->randomElement($arabicFirstNames) . ' ' . fake()->randomElement(['حسن', 'محمود', 'السيد']),
    40	            'name_en' => fake()->name($gender),
    41	            'national_id' => $nationalId,
    42	            'date_of_birth' => $birthDate,
    43	            'gender' => $gender,
    44	            'relation' => $relation,
    45	            'phone' => fake()->optional(0.3)->regexify('01[0125][0-9]{8}'),
    46	            'status' => 'active',
    47	            'is_archived' => false,
    48	        ];
    49	    }
    50	
    51	    public function spouse(): static
    52	    {
    53	        return $this->state(fn () => [
    54	            'relation' => 'spouse',
    55	            'date_of_birth' => fake()->dateTimeBetween('-60 years', '-18 years'),
    56	        ]);
    57	    }
    58	
    59	    public function child(): static
    60	    {
    61	        return $this->state(fn () => [
    62	            'relation' => 'child',
    63	            'date_of_birth' => fake()->dateTimeBetween('-25 years', '-1 year'),
    64	        ]);
    65	    }
    66	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [395]: database/factories/DependentFactory.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [396/494]: database/factories/MemberFactory.php
│ LANGUAGE: php | LINES: 96 | SIZE: 4375 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace Database\Factories;
     4	
     5	use App\Models\Governorate;
     6	use App\Models\MaritalStatus;
     7	use App\Models\Member;
     8	use App\Models\MembershipStatus;
     9	use App\Models\MembershipType;
    10	use App\Models\Nationality;
    11	use App\Models\Religion;
    12	use Illuminate\Database\Eloquent\Factories\Factory;
    13	
    14	class MemberFactory extends Factory
    15	{
    16	    protected $model = Member::class;
    17	
    18	    public function definition(): array
    19	    {
    20	        $gender = fake()->randomElement(['male', 'female']);
    21	        $birthDate = fake()->dateTimeBetween('-70 years', '-18 years');
    22	
    23	        // Generate a valid-ish 14-digit Egyptian national ID
    24	        $centuryDigit = $birthDate->format('Y') < 2000 ? '2' : '3';
    25	        $dobPart = $birthDate->format('ymd');
    26	        $govCode = str_pad((string) fake()->numberBetween(1, 27), 2, '0', STR_PAD_LEFT);
    27	        $seqPart = str_pad((string) fake()->numberBetween(1, 9999), 4, '0', STR_PAD_LEFT);
    28	        $genderDigit = $gender === 'male' ? fake()->randomElement([1, 3, 5, 7, 9]) : fake()->randomElement([2, 4, 6, 8]);
    29	        $nationalId = $centuryDigit . $dobPart . $govCode . $seqPart . $genderDigit;
    30	
    31	        $arabicFirstNames = $gender === 'male'
    32	            ? ['أحمد', 'محمد', 'عبد الله', 'يوسف', 'خالد', 'عمر', 'حسن', 'إبراهيم', 'علي', 'طارق']
    33	            : ['فاطمة', 'مريم', 'نور', 'سارة', 'هدى', 'أمينة', 'رانيا', 'دينا', 'ياسمين', 'ليلى'];
    34	
    35	        $arabicFamilyNames = ['حسن', 'عبد الرحمن', 'السيد', 'إبراهيم', 'محمود', 'عثمان', 'الشريف', 'البدوي', 'العربي', 'رمضان'];
    36	
    37	        $firstName = fake()->randomElement($arabicFirstNames);
    38	        $familyName = fake()->randomElement($arabicFamilyNames);
    39	
    40	        return [
    41	            'membership_number' => 'MEM-' . str_pad((string) fake()->unique()->numberBetween(1, 99999), 5, '0', STR_PAD_LEFT),
    42	            'name_ar' => "{$firstName} {$familyName}",
    43	            'name_en' => fake()->name($gender),
    44	            'national_id' => $nationalId,
    45	            'date_of_birth' => $birthDate,
    46	            'gender' => $gender,
    47	            'phone_primary' => '01' . fake()->randomElement(['0', '1', '2', '5']) . fake()->numerify('########'),
    48	            'phone_secondary' => fake()->optional(0.3)->regexify('01[0125][0-9]{8}'),
    49	            'email' => fake()->optional(0.5)->safeEmail(),
    50	            'address_street' => fake()->optional()->streetAddress(),
    51	            'address_district' => fake()->optional()->citySuffix(),
    52	            'address_city' => fake()->optional()->city(),
    53	            'address_governorate' => fake()->optional()->state(),
    54	            'employer_name' => fake()->optional(0.6)->company(),
    55	            'job_title' => fake()->optional(0.6)->jobTitle(),
    56	            'membership_type_id' => MembershipType::inRandomOrder()->value('id') ?? 1,
    57	            'status_id' => MembershipStatus::inRandomOrder()->value('id') ?? 1,
    58	            'nationality_id' => Nationality::where('code', 'EG')->value('id') ?? Nationality::inRandomOrder()->value('id'),
    59	            'religion_id' => Religion::inRandomOrder()->value('id'),
    60	            'marital_status_id' => MaritalStatus::inRandomOrder()->value('id'),
    61	            'governorate_id' => Governorate::inRandomOrder()->value('id'),
    62	            'application_date' => fake()->dateTimeBetween('-5 years', 'now'),
    63	            'is_archived' => false,
    64	        ];
    65	    }
    66	
    67	    public function active(): static
    68	    {
    69	        return $this->state(fn () => [
    70	            'status_id' => MembershipStatus::where('code', 'ACTIVE')->value('id'),
    71	            'activation_date' => fake()->dateTimeBetween('-4 years', '-1 month'),
    72	        ]);
    73	    }
    74	
    75	    public function suspended(): static
    76	    {
    77	        return $this->state(fn () => [
    78	            'status_id' => MembershipStatus::where('code', 'SUSPENDED')->value('id'),
    79	            'suspension_date' => fake()->dateTimeBetween('-1 year', 'now'),
    80	        ]);
    81	    }
    82	
    83	    public function pending(): static
    84	    {
    85	        return $this->state(fn () => [
    86	            'status_id' => MembershipStatus::where('code', 'NEW_APPLICATION')->value('id'),
    87	            'activation_date' => null,
    88	        ]);
    89	    }
    90	
    91	    public function working(): static
    92	    {
    93	        return $this->state(fn () => [
    94	            'membership_type_id' => MembershipType::where('code', 'WORKING')->value('id'),
    95	        ]);
    96	    }
    97	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [396]: database/factories/MemberFactory.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [397/494]: database/factories/ReceiptFactory.php
│ LANGUAGE: php | LINES: 40 | SIZE: 1208 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace Database\Factories;
     4	
     5	use App\Models\Member;
     6	use App\Models\Receipt;
     7	use Illuminate\Database\Eloquent\Factories\Factory;
     8	
     9	class ReceiptFactory extends Factory
    10	{
    11	    protected $model = Receipt::class;
    12	
    13	    public function definition(): array
    14	    {
    15	        $amount = fake()->randomFloat(2, 100, 50000);
    16	
    17	        return [
    18	            'receipt_number' => 'REC-' . str_pad((string) fake()->unique()->numberBetween(1, 99999), 6, '0', STR_PAD_LEFT),
    19	            'member_id' => Member::factory(),
    20	            'receipt_type' => 'payment',
    21	            'category' => fake()->randomElement(['application_fee', 'membership_value', 'subscription', 'fine', 'dependent']),
    22	            'subtotal' => $amount,
    23	            'discount_amount' => 0,
    24	            'total_amount' => $amount,
    25	            'amount_paid' => $amount,
    26	            'change_amount' => 0,
    27	            'status' => 'paid',
    28	            'is_void' => false,
    29	            'is_archived' => false,
    30	        ];
    31	    }
    32	
    33	    public function voided(): static
    34	    {
    35	        return $this->state(fn () => [
    36	            'is_void' => true,
    37	            'void_reason' => 'إيصال ملغي — اختبار',
    38	            'voided_at' => now(),
    39	        ]);
    40	    }
    41	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [397]: database/factories/ReceiptFactory.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [398/494]: database/factories/SubscriptionFactory.php
│ LANGUAGE: php | LINES: 37 | SIZE: 1032 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace Database\Factories;
     4	
     5	use App\Models\Member;
     6	use App\Models\Subscription;
     7	use Illuminate\Database\Eloquent\Factories\Factory;
     8	
     9	class SubscriptionFactory extends Factory
    10	{
    11	    protected $model = Subscription::class;
    12	
    13	    public function definition(): array
    14	    {
    15	        $year = fake()->numberBetween(2020, (int) now()->format('Y'));
    16	
    17	        return [
    18	            'member_id' => Member::factory(),
    19	            'year' => $year,
    20	            'amount' => fake()->randomFloat(2, 1000, 10000),
    21	            'amount_paid' => 0,
    22	            'status' => 'pending',
    23	            'due_date' => "{$year}-01-01",
    24	            'is_archived' => false,
    25	        ];
    26	    }
    27	
    28	    public function paid(): static
    29	    {
    30	        return $this->state(function (array $attributes) {
    31	            return [
    32	                'amount_paid' => $attributes['amount'],
    33	                'status' => 'paid',
    34	                'paid_date' => fake()->dateTimeBetween("{$attributes['year']}-01-01", "{$attributes['year']}-12-31"),
    35	            ];
    36	        });
    37	    }
    38	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [398]: database/factories/SubscriptionFactory.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [399/494]: database/migrations/2024_01_01_000001_create_membership_types_table.php
│ LANGUAGE: php | LINES: 35 | SIZE: 1337 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	use Illuminate\Database\Migrations\Migration;
     4	use Illuminate\Database\Schema\Blueprint;
     5	use Illuminate\Support\Facades\Schema;
     6	
     7	return new class extends Migration
     8	{
     9	    public function up(): void
    10	    {
    11	        Schema::create('membership_types', function (Blueprint $table) {
    12	            $table->id();
    13	            $table->string('name_ar');
    14	            $table->string('name_en')->nullable();
    15	            $table->string('code', 20)->unique();
    16	            $table->text('description_ar')->nullable();
    17	            $table->text('description_en')->nullable();
    18	            $table->decimal('application_fee', 12, 2)->default(0);
    19	            $table->decimal('membership_value', 12, 2)->default(0);
    20	            $table->decimal('annual_subscription', 12, 2)->default(0);
    21	            $table->integer('max_dependents')->nullable();
    22	            $table->boolean('allows_voting')->default(false);
    23	            $table->boolean('allows_election')->default(false);
    24	            $table->boolean('is_transferable')->default(false);
    25	            $table->integer('sort_order')->default(0);
    26	            $table->boolean('is_active')->default(true);
    27	            $table->boolean('is_archived')->default(false);
    28	            $table->timestamps();
    29	        });
    30	    }
    31	
    32	    public function down(): void
    33	    {
    34	        Schema::dropIfExists('membership_types');
    35	    }
    36	};│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [399]: database/migrations/2024_01_01_000001_create_membership_types_table.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [400/494]: database/migrations/2024_01_01_000002_create_membership_statuses_table.php
│ LANGUAGE: php | LINES: 32 | SIZE: 1156 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	use Illuminate\Database\Migrations\Migration;
     4	use Illuminate\Database\Schema\Blueprint;
     5	use Illuminate\Support\Facades\Schema;
     6	
     7	return new class extends Migration
     8	{
     9	    public function up(): void
    10	    {
    11	        Schema::create('membership_statuses', function (Blueprint $table) {
    12	            $table->id();
    13	            $table->string('name_ar');
    14	            $table->string('name_en')->nullable();
    15	            $table->string('code', 30)->unique();
    16	            $table->string('color', 20)->nullable();
    17	            $table->string('icon', 50)->nullable();
    18	            $table->text('description_ar')->nullable();
    19	            $table->boolean('allows_entry')->default(false);
    20	            $table->boolean('allows_renewal')->default(false);
    21	            $table->boolean('is_terminal')->default(false)->comment('No further transitions');
    22	            $table->integer('sort_order')->default(0);
    23	            $table->boolean('is_active')->default(true);
    24	            $table->boolean('is_archived')->default(false);
    25	            $table->timestamps();
    26	        });
    27	    }
    28	
    29	    public function down(): void
    30	    {
    31	        Schema::dropIfExists('membership_statuses');
    32	    }
    33	};│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [400]: database/migrations/2024_01_01_000002_create_membership_statuses_table.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [401/494]: database/migrations/2024_01_01_000003_create_nationalities_table.php
│ LANGUAGE: php | LINES: 27 | SIZE: 873 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	use Illuminate\Database\Migrations\Migration;
     4	use Illuminate\Database\Schema\Blueprint;
     5	use Illuminate\Support\Facades\Schema;
     6	
     7	return new class extends Migration
     8	{
     9	    public function up(): void
    10	    {
    11	        Schema::create('nationalities', function (Blueprint $table) {
    12	            $table->id();
    13	            $table->string('name_ar');
    14	            $table->string('name_en')->nullable();
    15	            $table->string('code', 10)->unique();
    16	            $table->string('demonym_ar')->nullable()->comment('اسم النسبة: مصري، سعودي');
    17	            $table->integer('sort_order')->default(0);
    18	            $table->boolean('is_active')->default(true);
    19	            $table->boolean('is_archived')->default(false);
    20	            $table->timestamps();
    21	        });
    22	    }
    23	
    24	    public function down(): void
    25	    {
    26	        Schema::dropIfExists('nationalities');
    27	    }
    28	};│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [401]: database/migrations/2024_01_01_000003_create_nationalities_table.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [402/494]: database/migrations/2024_01_01_000004_create_religions_table.php
│ LANGUAGE: php | LINES: 25 | SIZE: 706 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	use Illuminate\Database\Migrations\Migration;
     4	use Illuminate\Database\Schema\Blueprint;
     5	use Illuminate\Support\Facades\Schema;
     6	
     7	return new class extends Migration
     8	{
     9	    public function up(): void
    10	    {
    11	        Schema::create('religions', function (Blueprint $table) {
    12	            $table->id();
    13	            $table->string('name_ar');
    14	            $table->string('name_en')->nullable();
    15	            $table->integer('sort_order')->default(0);
    16	            $table->boolean('is_active')->default(true);
    17	            $table->boolean('is_archived')->default(false);
    18	            $table->timestamps();
    19	        });
    20	    }
    21	
    22	    public function down(): void
    23	    {
    24	        Schema::dropIfExists('religions');
    25	    }
    26	};│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [402]: database/migrations/2024_01_01_000004_create_religions_table.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [403/494]: database/migrations/2024_01_01_000005_create_marital_statuses_table.php
│ LANGUAGE: php | LINES: 25 | SIZE: 720 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	use Illuminate\Database\Migrations\Migration;
     4	use Illuminate\Database\Schema\Blueprint;
     5	use Illuminate\Support\Facades\Schema;
     6	
     7	return new class extends Migration
     8	{
     9	    public function up(): void
    10	    {
    11	        Schema::create('marital_statuses', function (Blueprint $table) {
    12	            $table->id();
    13	            $table->string('name_ar');
    14	            $table->string('name_en')->nullable();
    15	            $table->integer('sort_order')->default(0);
    16	            $table->boolean('is_active')->default(true);
    17	            $table->boolean('is_archived')->default(false);
    18	            $table->timestamps();
    19	        });
    20	    }
    21	
    22	    public function down(): void
    23	    {
    24	        Schema::dropIfExists('marital_statuses');
    25	    }
    26	};│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [403]: database/migrations/2024_01_01_000005_create_marital_statuses_table.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [404/494]: database/migrations/2024_01_01_000006_create_governorates_table.php
│ LANGUAGE: php | LINES: 26 | SIZE: 762 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	use Illuminate\Database\Migrations\Migration;
     4	use Illuminate\Database\Schema\Blueprint;
     5	use Illuminate\Support\Facades\Schema;
     6	
     7	return new class extends Migration
     8	{
     9	    public function up(): void
    10	    {
    11	        Schema::create('governorates', function (Blueprint $table) {
    12	            $table->id();
    13	            $table->string('name_ar');
    14	            $table->string('name_en')->nullable();
    15	            $table->string('code', 10)->unique();
    16	            $table->integer('sort_order')->default(0);
    17	            $table->boolean('is_active')->default(true);
    18	            $table->boolean('is_archived')->default(false);
    19	            $table->timestamps();
    20	        });
    21	    }
    22	
    23	    public function down(): void
    24	    {
    25	        Schema::dropIfExists('governorates');
    26	    }
    27	};│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [404]: database/migrations/2024_01_01_000006_create_governorates_table.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [405/494]: database/migrations/2024_01_01_000007_create_educational_qualifications_table.php
│ LANGUAGE: php | LINES: 25 | SIZE: 740 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	use Illuminate\Database\Migrations\Migration;
     4	use Illuminate\Database\Schema\Blueprint;
     5	use Illuminate\Support\Facades\Schema;
     6	
     7	return new class extends Migration
     8	{
     9	    public function up(): void
    10	    {
    11	        Schema::create('educational_qualifications', function (Blueprint $table) {
    12	            $table->id();
    13	            $table->string('name_ar');
    14	            $table->string('name_en')->nullable();
    15	            $table->integer('sort_order')->default(0);
    16	            $table->boolean('is_active')->default(true);
    17	            $table->boolean('is_archived')->default(false);
    18	            $table->timestamps();
    19	        });
    20	    }
    21	
    22	    public function down(): void
    23	    {
    24	        Schema::dropIfExists('educational_qualifications');
    25	    }
    26	};│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [405]: database/migrations/2024_01_01_000007_create_educational_qualifications_table.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [406/494]: database/migrations/2024_01_01_000008_create_document_types_table.php
│ LANGUAGE: php | LINES: 32 | SIZE: 1190 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	use Illuminate\Database\Migrations\Migration;
     4	use Illuminate\Database\Schema\Blueprint;
     5	use Illuminate\Support\Facades\Schema;
     6	
     7	return new class extends Migration
     8	{
     9	    public function up(): void
    10	    {
    11	        Schema::create('document_types', function (Blueprint $table) {
    12	            $table->id();
    13	            $table->string('name_ar');
    14	            $table->string('name_en')->nullable();
    15	            $table->string('code', 30)->unique();
    16	            $table->text('description_ar')->nullable();
    17	            $table->boolean('is_required_for_application')->default(false);
    18	            $table->boolean('is_required_for_activation')->default(false);
    19	            $table->boolean('is_required_for_dependent')->default(false);
    20	            $table->string('allowed_extensions')->default('pdf,jpg,jpeg,png');
    21	            $table->integer('max_file_size_kb')->default(5120);
    22	            $table->integer('sort_order')->default(0);
    23	            $table->boolean('is_active')->default(true);
    24	            $table->boolean('is_archived')->default(false);
    25	            $table->timestamps();
    26	        });
    27	    }
    28	
    29	    public function down(): void
    30	    {
    31	        Schema::dropIfExists('document_types');
    32	    }
    33	};│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [406]: database/migrations/2024_01_01_000008_create_document_types_table.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [407/494]: database/migrations/2024_01_01_000010_create_members_table.php
│ LANGUAGE: php | LINES: 89 | SIZE: 4065 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	use Illuminate\Database\Migrations\Migration;
     4	use Illuminate\Database\Schema\Blueprint;
     5	use Illuminate\Support\Facades\Schema;
     6	
     7	return new class extends Migration
     8	{
     9	    public function up(): void
    10	    {
    11	        Schema::create('members', function (Blueprint $table) {
    12	            $table->id();
    13	            $table->string('membership_number', 50)->unique();
    14	            $table->string('legacy_number', 50)->nullable()->unique()->comment('رقم العضوية القديم');
    15	
    16	            // — Identity —
    17	            $table->string('name_ar');
    18	            $table->string('name_en')->nullable();
    19	            $table->string('national_id', 14)->unique();
    20	            $table->date('date_of_birth')->nullable();
    21	            $table->string('gender', 10)->nullable();
    22	            $table->string('blood_type', 5)->nullable();
    23	
    24	            // — Foreign Keys to Lookups —
    25	            $table->foreignId('membership_type_id')->constrained('membership_types');
    26	            $table->foreignId('status_id')->constrained('membership_statuses');
    27	            $table->foreignId('nationality_id')->nullable()->constrained('nationalities')->nullOnDelete();
    28	            $table->foreignId('religion_id')->nullable()->constrained('religions')->nullOnDelete();
    29	            $table->foreignId('marital_status_id')->nullable()->constrained('marital_statuses')->nullOnDelete();
    30	            $table->foreignId('governorate_id')->nullable()->constrained('governorates')->nullOnDelete();
    31	            $table->foreignId('educational_qualification_id')->nullable()->constrained('educational_qualifications')->nullOnDelete();
    32	
    33	            // — Contact —
    34	            $table->string('phone_primary', 20)->nullable();
    35	            $table->string('phone_secondary', 20)->nullable();
    36	            $table->string('email')->nullable();
    37	
    38	            // — Address —
    39	            $table->string('address_street')->nullable();
    40	            $table->string('address_district')->nullable();
    41	            $table->string('address_city')->nullable();
    42	            $table->string('address_governorate')->nullable();
    43	            $table->string('address_postal_code', 10)->nullable();
    44	
    45	            // — Employment —
    46	            $table->string('employer_name')->nullable();
    47	            $table->string('job_title')->nullable();
    48	            $table->string('employer_phone', 20)->nullable();
    49	            $table->string('employer_address')->nullable();
    50	
    51	            // — Membership Lifecycle Dates —
    52	            $table->date('application_date')->nullable();
    53	            $table->date('approval_date')->nullable();
    54	            $table->date('activation_date')->nullable();
    55	            $table->date('suspension_date')->nullable();
    56	            $table->date('reinstatement_date')->nullable();
    57	            $table->date('termination_date')->nullable();
    58	            $table->date('last_renewal_date')->nullable();
    59	            $table->year('last_renewal_year')->nullable();
    60	
    61	            // — Workflow —
    62	            $table->foreignId('current_workflow_stage_id')->nullable()->constrained('workflow_stages')->nullOnDelete();
    63	
    64	            // — Photos —
    65	            $table->string('photo_path')->nullable();
    66	
    67	            // — Meta —
    68	            $table->text('notes')->nullable();
    69	            $table->foreignId('created_by')->nullable()->constrained('users')->nullOnDelete();
    70	            $table->foreignId('updated_by')->nullable()->constrained('users')->nullOnDelete();
    71	            $table->boolean('is_archived')->default(false);
    72	            $table->timestamp('archived_at')->nullable();
    73	            $table->foreignId('archived_by')->nullable()->constrained('users')->nullOnDelete();
    74	            $table->timestamps();
    75	            $table->softDeletes();
    76	
    77	            // — Indexes —
    78	            $table->index('national_id');
    79	            $table->index('status_id');
    80	            $table->index('membership_type_id');
    81	            $table->index('phone_primary');
    82	            $table->index('is_archived');
    83	        });
    84	    }
    85	
    86	    public function down(): void
    87	    {
    88	        Schema::dropIfExists('members');
    89	    }
    90	};│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [407]: database/migrations/2024_01_01_000010_create_members_table.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [408/494]: database/migrations/2024_01_01_000011_create_documents_table.php
│ LANGUAGE: php | LINES: 42 | SIZE: 1729 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	use Illuminate\Database\Migrations\Migration;
     4	use Illuminate\Database\Schema\Blueprint;
     5	use Illuminate\Support\Facades\Schema;
     6	
     7	return new class extends Migration
     8	{
     9	    public function up(): void
    10	    {
    11	        Schema::create('documents', function (Blueprint $table) {
    12	            $table->id();
    13	            $table->string('document_number', 50)->unique();
    14	            $table->foreignId('member_id')->constrained('members')->cascadeOnDelete();
    15	            $table->foreignId('dependent_id')->nullable()->constrained('dependents')->nullOnDelete();
    16	            $table->foreignId('document_type_id')->constrained('document_types');
    17	            $table->string('title');
    18	            $table->string('file_path');
    19	            $table->string('file_name');
    20	            $table->string('file_extension', 10);
    21	            $table->unsignedInteger('file_size_kb');
    22	            $table->string('mime_type', 100);
    23	            $table->text('description')->nullable();
    24	            $table->date('issue_date')->nullable();
    25	            $table->date('expiry_date')->nullable();
    26	            $table->boolean('is_verified')->default(false);
    27	            $table->foreignId('verified_by')->nullable()->constrained('users')->nullOnDelete();
    28	            $table->timestamp('verified_at')->nullable();
    29	            $table->text('verification_notes')->nullable();
    30	            $table->foreignId('created_by')->nullable()->constrained('users')->nullOnDelete();
    31	            $table->boolean('is_archived')->default(false);
    32	            $table->timestamps();
    33	            $table->softDeletes();
    34	
    35	            $table->index(['member_id', 'document_type_id']);
    36	        });
    37	    }
    38	
    39	    public function down(): void
    40	    {
    41	        Schema::dropIfExists('documents');
    42	    }
    43	};│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [408]: database/migrations/2024_01_01_000011_create_documents_table.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [409/494]: database/migrations/2024_01_01_000012_create_cards_table.php
│ LANGUAGE: php | LINES: 42 | SIZE: 1794 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	use Illuminate\Database\Migrations\Migration;
     4	use Illuminate\Database\Schema\Blueprint;
     5	use Illuminate\Support\Facades\Schema;
     6	
     7	return new class extends Migration
     8	{
     9	    public function up(): void
    10	    {
    11	        Schema::create('cards', function (Blueprint $table) {
    12	            $table->id();
    13	            $table->string('card_number', 50)->unique();
    14	            $table->foreignId('member_id')->constrained('members')->cascadeOnDelete();
    15	            $table->foreignId('dependent_id')->nullable()->constrained('dependents')->nullOnDelete();
    16	            $table->string('card_type', 30)->comment('member, dependent, temporary');
    17	            $table->string('status', 20)->default('active');
    18	            $table->year('valid_for_year');
    19	            $table->date('issue_date');
    20	            $table->date('expiry_date')->nullable();
    21	            $table->integer('print_count')->default(0);
    22	            $table->timestamp('last_printed_at')->nullable();
    23	            $table->foreignId('printed_by')->nullable()->constrained('users')->nullOnDelete();
    24	            $table->string('photo_path')->nullable();
    25	            $table->string('qr_code_data')->nullable();
    26	            $table->text('cancellation_reason')->nullable();
    27	            $table->timestamp('cancelled_at')->nullable();
    28	            $table->foreignId('cancelled_by')->nullable()->constrained('users')->nullOnDelete();
    29	            $table->foreignId('created_by')->nullable()->constrained('users')->nullOnDelete();
    30	            $table->boolean('is_archived')->default(false);
    31	            $table->timestamps();
    32	            $table->softDeletes();
    33	
    34	            $table->index(['member_id', 'valid_for_year']);
    35	            $table->index('status');
    36	        });
    37	    }
    38	
    39	    public function down(): void
    40	    {
    41	        Schema::dropIfExists('cards');
    42	    }
    43	};│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [409]: database/migrations/2024_01_01_000012_create_cards_table.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [410/494]: database/migrations/2024_01_01_000013_create_receipts_table.php
│ LANGUAGE: php | LINES: 52 | SIZE: 2622 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	use Illuminate\Database\Migrations\Migration;
     4	use Illuminate\Database\Schema\Blueprint;
     5	use Illuminate\Support\Facades\Schema;
     6	
     7	return new class extends Migration
     8	{
     9	    public function up(): void
    10	    {
    11	        Schema::create('receipts', function (Blueprint $table) {
    12	            $table->id();
    13	            $table->string('receipt_number', 50)->unique();
    14	            $table->foreignId('member_id')->constrained('members');
    15	            $table->foreignId('dependent_id')->nullable()->constrained('dependents')->nullOnDelete();
    16	            $table->foreignId('cash_register_id')->nullable()->constrained('cash_registers')->nullOnDelete();
    17	            $table->foreignId('payment_method_id')->nullable()->constrained('payment_methods')->nullOnDelete();
    18	            $table->string('receipt_type', 30)->comment('payment, refund, adjustment');
    19	            $table->string('category', 50)->comment('application_fee, membership_value, subscription, fine, etc.');
    20	            $table->decimal('subtotal', 12, 2)->default(0);
    21	            $table->decimal('discount_amount', 12, 2)->default(0);
    22	            $table->string('discount_reason')->nullable();
    23	            $table->foreignId('board_offer_id')->nullable()->constrained('board_offers')->nullOnDelete();
    24	            $table->decimal('total_amount', 12, 2);
    25	            $table->decimal('amount_paid', 12, 2)->default(0);
    26	            $table->decimal('change_amount', 12, 2)->default(0);
    27	            $table->string('status', 20)->default('paid');
    28	            $table->text('notes')->nullable();
    29	            $table->string('reference_type')->nullable()->comment('polymorphic: subscription, violation, etc.');
    30	            $table->unsignedBigInteger('reference_id')->nullable();
    31	            $table->integer('print_count')->default(0);
    32	            $table->timestamp('last_printed_at')->nullable();
    33	            $table->boolean('is_void')->default(false);
    34	            $table->text('void_reason')->nullable();
    35	            $table->foreignId('voided_by')->nullable()->constrained('users')->nullOnDelete();
    36	            $table->timestamp('voided_at')->nullable();
    37	            $table->foreignId('created_by')->nullable()->constrained('users')->nullOnDelete();
    38	            $table->boolean('is_archived')->default(false);
    39	            $table->timestamps();
    40	            $table->softDeletes();
    41	
    42	            $table->index(['member_id', 'receipt_type']);
    43	            $table->index(['reference_type', 'reference_id']);
    44	            $table->index('status');
    45	            $table->index('category');
    46	        });
    47	    }
    48	
    49	    public function down(): void
    50	    {
    51	        Schema::dropIfExists('receipts');
    52	    }
    53	};│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [410]: database/migrations/2024_01_01_000013_create_receipts_table.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [411/494]: database/migrations/2024_01_01_000014_create_receipt_items_table.php
│ LANGUAGE: php | LINES: 29 | SIZE: 958 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	use Illuminate\Database\Migrations\Migration;
     4	use Illuminate\Database\Schema\Blueprint;
     5	use Illuminate\Support\Facades\Schema;
     6	
     7	return new class extends Migration
     8	{
     9	    public function up(): void
    10	    {
    11	        Schema::create('receipt_items', function (Blueprint $table) {
    12	            $table->id();
    13	            $table->foreignId('receipt_id')->constrained('receipts')->cascadeOnDelete();
    14	            $table->string('description_ar');
    15	            $table->string('description_en')->nullable();
    16	            $table->string('category', 50);
    17	            $table->decimal('quantity', 8, 2)->default(1);
    18	            $table->decimal('unit_price', 12, 2);
    19	            $table->decimal('discount', 12, 2)->default(0);
    20	            $table->decimal('total', 12, 2);
    21	            $table->integer('sort_order')->default(0);
    22	            $table->timestamps();
    23	        });
    24	    }
    25	
    26	    public function down(): void
    27	    {
    28	        Schema::dropIfExists('receipt_items');
    29	    }
    30	};│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [411]: database/migrations/2024_01_01_000014_create_receipt_items_table.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [412/494]: database/migrations/2024_01_01_000015_create_fee_structures_table.php
│ LANGUAGE: php | LINES: 44 | SIZE: 1969 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	use Illuminate\Database\Migrations\Migration;
     4	use Illuminate\Database\Schema\Blueprint;
     5	use Illuminate\Support\Facades\Schema;
     6	
     7	return new class extends Migration
     8	{
     9	    public function up(): void
    10	    {
    11	        Schema::create('fee_structures', function (Blueprint $table) {
    12	            $table->id();
    13	            $table->string('name_ar');
    14	            $table->string('name_en')->nullable();
    15	            $table->string('code', 50)->unique();
    16	            $table->foreignId('membership_type_id')->nullable()->constrained('membership_types')->nullOnDelete();
    17	            $table->string('fee_category', 50)->comment('application, membership_value, subscription, dependent, transfer, etc.');
    18	            $table->string('applies_to', 30)->default('member')->comment('member, dependent, both');
    19	            $table->decimal('amount', 12, 2)->default(0);
    20	            $table->boolean('is_percentage')->default(false);
    21	            $table->decimal('percentage_base', 12, 2)->nullable();
    22	            $table->integer('age_min')->nullable();
    23	            $table->integer('age_max')->nullable();
    24	            $table->string('dependent_relation')->nullable()->comment('spouse, child, parent');
    25	            $table->string('gender_restriction', 10)->nullable();
    26	            $table->date('effective_from')->nullable();
    27	            $table->date('effective_to')->nullable();
    28	            $table->text('description_ar')->nullable();
    29	            $table->integer('sort_order')->default(0);
    30	            $table->boolean('is_active')->default(true);
    31	            $table->boolean('is_archived')->default(false);
    32	            $table->foreignId('created_by')->nullable()->constrained('users')->nullOnDelete();
    33	            $table->timestamps();
    34	            $table->softDeletes();
    35	
    36	            $table->index(['membership_type_id', 'fee_category']);
    37	            $table->index('is_active');
    38	        });
    39	    }
    40	
    41	    public function down(): void
    42	    {
    43	        Schema::dropIfExists('fee_structures');
    44	    }
    45	};│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [412]: database/migrations/2024_01_01_000015_create_fee_structures_table.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [413/494]: database/migrations/2024_01_01_000016_create_workflow_stages_table.php
│ LANGUAGE: php | LINES: 42 | SIZE: 1749 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	use Illuminate\Database\Migrations\Migration;
     4	use Illuminate\Database\Schema\Blueprint;
     5	use Illuminate\Support\Facades\Schema;
     6	
     7	return new class extends Migration
     8	{
     9	    public function up(): void
    10	    {
    11	        Schema::create('workflow_stages', function (Blueprint $table) {
    12	            $table->id();
    13	            $table->string('workflow_type', 50)->comment('new_membership, renewal, transfer, etc.');
    14	            $table->string('name_ar');
    15	            $table->string('name_en')->nullable();
    16	            $table->string('code', 50);
    17	            $table->integer('stage_order');
    18	            $table->text('description_ar')->nullable();
    19	            $table->json('gate_conditions')->nullable()->comment('JSON conditions that must be met');
    20	            $table->json('allowed_roles')->nullable();
    21	            $table->json('required_documents')->nullable();
    22	            $table->boolean('requires_payment')->default(false);
    23	            $table->string('payment_category')->nullable();
    24	            $table->integer('timeout_days')->nullable();
    25	            $table->string('timeout_action', 30)->nullable()->comment('expire, notify, escalate');
    26	            $table->string('notification_template')->nullable();
    27	            $table->string('error_message_ar')->nullable();
    28	            $table->string('icon', 50)->nullable();
    29	            $table->string('color', 20)->nullable();
    30	            $table->boolean('is_active')->default(true);
    31	            $table->boolean('is_archived')->default(false);
    32	            $table->timestamps();
    33	
    34	            $table->unique(['workflow_type', 'code']);
    35	            $table->index(['workflow_type', 'stage_order']);
    36	        });
    37	    }
    38	
    39	    public function down(): void
    40	    {
    41	        Schema::dropIfExists('workflow_stages');
    42	    }
    43	};│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [413]: database/migrations/2024_01_01_000016_create_workflow_stages_table.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [414/494]: database/migrations/2024_01_01_000017_create_interviews_table.php
│ LANGUAGE: php | LINES: 35 | SIZE: 1395 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	use Illuminate\Database\Migrations\Migration;
     4	use Illuminate\Database\Schema\Blueprint;
     5	use Illuminate\Support\Facades\Schema;
     6	
     7	return new class extends Migration
     8	{
     9	    public function up(): void
    10	    {
    11	        Schema::create('interviews', function (Blueprint $table) {
    12	            $table->id();
    13	            $table->foreignId('member_id')->constrained('members')->cascadeOnDelete();
    14	            $table->date('scheduled_date');
    15	            $table->time('scheduled_time')->nullable();
    16	            $table->string('status', 20)->default('scheduled')->comment('scheduled, completed, cancelled, no_show');
    17	            $table->string('location')->nullable();
    18	            $table->foreignId('interviewer_id')->nullable()->constrained('users')->nullOnDelete();
    19	            $table->text('notes')->nullable();
    20	            $table->text('result_notes')->nullable();
    21	            $table->string('result', 20)->nullable()->comment('passed, failed, deferred');
    22	            $table->timestamp('completed_at')->nullable();
    23	            $table->foreignId('created_by')->nullable()->constrained('users')->nullOnDelete();
    24	            $table->boolean('is_archived')->default(false);
    25	            $table->timestamps();
    26	            $table->softDeletes();
    27	
    28	            $table->index(['member_id', 'status']);
    29	        });
    30	    }
    31	
    32	    public function down(): void
    33	    {
    34	        Schema::dropIfExists('interviews');
    35	    }
    36	};│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [414]: database/migrations/2024_01_01_000017_create_interviews_table.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [415/494]: database/migrations/2024_01_01_000018_create_transfers_table.php
│ LANGUAGE: php | LINES: 44 | SIZE: 2039 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	use Illuminate\Database\Migrations\Migration;
     4	use Illuminate\Database\Schema\Blueprint;
     5	use Illuminate\Support\Facades\Schema;
     6	
     7	return new class extends Migration
     8	{
     9	    public function up(): void
    10	    {
    11	        Schema::create('transfers', function (Blueprint $table) {
    12	            $table->id();
    13	            $table->string('transfer_number', 50)->unique();
    14	            $table->foreignId('from_member_id')->constrained('members');
    15	            $table->foreignId('to_member_id')->nullable()->constrained('members')->nullOnDelete();
    16	            $table->string('transfer_type', 30)->comment('death, divorce, board_decision');
    17	            $table->string('status', 20)->default('pending');
    18	            $table->date('event_date')->comment('date of death/divorce/decision');
    19	            $table->date('application_date');
    20	            $table->date('deadline_date')->nullable()->comment('e.g., 1 year from divorce');
    21	            $table->date('approval_date')->nullable();
    22	            $table->date('completion_date')->nullable();
    23	            $table->decimal('transfer_fee', 12, 2)->default(0);
    24	            $table->foreignId('receipt_id')->nullable()->constrained('receipts')->nullOnDelete();
    25	            $table->string('certificate_document_id')->nullable()->comment('death/divorce certificate');
    26	            $table->json('heir_details')->nullable();
    27	            $table->json('custody_allocation')->nullable();
    28	            $table->text('notes')->nullable();
    29	            $table->text('rejection_reason')->nullable();
    30	            $table->foreignId('approved_by')->nullable()->constrained('users')->nullOnDelete();
    31	            $table->foreignId('created_by')->nullable()->constrained('users')->nullOnDelete();
    32	            $table->boolean('is_archived')->default(false);
    33	            $table->timestamps();
    34	            $table->softDeletes();
    35	
    36	            $table->index(['from_member_id', 'transfer_type']);
    37	            $table->index('status');
    38	        });
    39	    }
    40	
    41	    public function down(): void
    42	    {
    43	        Schema::dropIfExists('transfers');
    44	    }
    45	};│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [415]: database/migrations/2024_01_01_000018_create_transfers_table.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [416/494]: database/migrations/2024_01_01_000019_create_installment_plans_table.php
│ LANGUAGE: php | LINES: 42 | SIZE: 1774 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	use Illuminate\Database\Migrations\Migration;
     4	use Illuminate\Database\Schema\Blueprint;
     5	use Illuminate\Support\Facades\Schema;
     6	
     7	return new class extends Migration
     8	{
     9	    public function up(): void
    10	    {
    11	        Schema::create('installment_plans', function (Blueprint $table) {
    12	            $table->id();
    13	            $table->string('plan_number', 50)->unique();
    14	            $table->foreignId('member_id')->constrained('members');
    15	            $table->foreignId('receipt_id')->nullable()->constrained('receipts')->nullOnDelete();
    16	            $table->string('plan_type', 50)->comment('membership_value, subscription_arrears, etc.');
    17	            $table->string('status', 20)->default('active');
    18	            $table->decimal('total_amount', 12, 2);
    19	            $table->decimal('down_payment', 12, 2)->default(0);
    20	            $table->decimal('remaining_amount', 12, 2);
    21	            $table->integer('number_of_installments');
    22	            $table->decimal('installment_amount', 12, 2);
    23	            $table->date('start_date');
    24	            $table->date('end_date');
    25	            $table->integer('paid_installments')->default(0);
    26	            $table->decimal('total_paid', 12, 2)->default(0);
    27	            $table->date('next_due_date')->nullable();
    28	            $table->text('notes')->nullable();
    29	            $table->foreignId('approved_by')->nullable()->constrained('users')->nullOnDelete();
    30	            $table->foreignId('created_by')->nullable()->constrained('users')->nullOnDelete();
    31	            $table->boolean('is_archived')->default(false);
    32	            $table->timestamps();
    33	            $table->softDeletes();
    34	
    35	            $table->index(['member_id', 'status']);
    36	        });
    37	    }
    38	
    39	    public function down(): void
    40	    {
    41	        Schema::dropIfExists('installment_plans');
    42	    }
    43	};│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [416]: database/migrations/2024_01_01_000019_create_installment_plans_table.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [417/494]: database/migrations/2024_01_01_000020_create_installments_table.php
│ LANGUAGE: php | LINES: 32 | SIZE: 1166 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	use Illuminate\Database\Migrations\Migration;
     4	use Illuminate\Database\Schema\Blueprint;
     5	use Illuminate\Support\Facades\Schema;
     6	
     7	return new class extends Migration
     8	{
     9	    public function up(): void
    10	    {
    11	        Schema::create('installments', function (Blueprint $table) {
    12	            $table->id();
    13	            $table->foreignId('installment_plan_id')->constrained('installment_plans')->cascadeOnDelete();
    14	            $table->integer('installment_number');
    15	            $table->decimal('amount', 12, 2);
    16	            $table->date('due_date');
    17	            $table->date('paid_date')->nullable();
    18	            $table->decimal('amount_paid', 12, 2)->default(0);
    19	            $table->string('status', 20)->default('pending')->comment('pending, paid, overdue, partially_paid');
    20	            $table->foreignId('receipt_id')->nullable()->constrained('receipts')->nullOnDelete();
    21	            $table->text('notes')->nullable();
    22	            $table->timestamps();
    23	
    24	            $table->index(['installment_plan_id', 'status']);
    25	            $table->index('due_date');
    26	        });
    27	    }
    28	
    29	    public function down(): void
    30	    {
    31	        Schema::dropIfExists('installments');
    32	    }
    33	};│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [417]: database/migrations/2024_01_01_000020_create_installments_table.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [418/494]: database/migrations/2024_01_01_000021_create_board_offers_table.php
│ LANGUAGE: php | LINES: 44 | SIZE: 1912 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	use Illuminate\Database\Migrations\Migration;
     4	use Illuminate\Database\Schema\Blueprint;
     5	use Illuminate\Support\Facades\Schema;
     6	
     7	return new class extends Migration
     8	{
     9	    public function up(): void
    10	    {
    11	        Schema::create('board_offers', function (Blueprint $table) {
    12	            $table->id();
    13	            $table->string('offer_number', 50)->unique();
    14	            $table->string('name_ar');
    15	            $table->string('name_en')->nullable();
    16	            $table->text('description_ar')->nullable();
    17	            $table->string('offer_type', 30)->comment('discount_percentage, discount_fixed, fee_waiver, special_rate');
    18	            $table->string('status', 20)->default('draft');
    19	            $table->decimal('discount_percentage', 5, 2)->nullable();
    20	            $table->decimal('discount_amount', 12, 2)->nullable();
    21	            $table->json('applies_to_categories')->nullable()->comment('which fee categories');
    22	            $table->json('applies_to_membership_types')->nullable();
    23	            $table->date('start_date');
    24	            $table->date('end_date');
    25	            $table->integer('max_usage')->nullable()->comment('null = unlimited');
    26	            $table->integer('current_usage')->default(0);
    27	            $table->text('board_resolution_number')->nullable();
    28	            $table->date('board_resolution_date')->nullable();
    29	            $table->text('terms_ar')->nullable();
    30	            $table->foreignId('created_by')->nullable()->constrained('users')->nullOnDelete();
    31	            $table->foreignId('approved_by')->nullable()->constrained('users')->nullOnDelete();
    32	            $table->boolean('is_archived')->default(false);
    33	            $table->timestamps();
    34	            $table->softDeletes();
    35	
    36	            $table->index('status');
    37	            $table->index(['start_date', 'end_date']);
    38	        });
    39	    }
    40	
    41	    public function down(): void
    42	    {
    43	        Schema::dropIfExists('board_offers');
    44	    }
    45	};│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [418]: database/migrations/2024_01_01_000021_create_board_offers_table.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [419/494]: database/migrations/2024_01_01_000022_create_bookings_table.php
│ LANGUAGE: php | LINES: 44 | SIZE: 1918 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	use Illuminate\Database\Migrations\Migration;
     4	use Illuminate\Database\Schema\Blueprint;
     5	use Illuminate\Support\Facades\Schema;
     6	
     7	return new class extends Migration
     8	{
     9	    public function up(): void
    10	    {
    11	        Schema::create('bookings', function (Blueprint $table) {
    12	            $table->id();
    13	            $table->string('booking_number', 50)->unique();
    14	            $table->foreignId('member_id')->constrained('members');
    15	            $table->foreignId('dependent_id')->nullable()->constrained('dependents')->nullOnDelete();
    16	            $table->foreignId('facility_id')->constrained('facilities');
    17	            $table->string('status', 20)->default('confirmed');
    18	            $table->date('booking_date');
    19	            $table->time('start_time');
    20	            $table->time('end_time');
    21	            $table->decimal('duration_hours', 5, 2)->nullable();
    22	            $table->decimal('amount', 12, 2)->default(0);
    23	            $table->foreignId('receipt_id')->nullable()->constrained('receipts')->nullOnDelete();
    24	            $table->timestamp('checked_in_at')->nullable();
    25	            $table->foreignId('checked_in_by')->nullable()->constrained('users')->nullOnDelete();
    26	            $table->text('notes')->nullable();
    27	            $table->timestamp('cancelled_at')->nullable();
    28	            $table->foreignId('cancelled_by')->nullable()->constrained('users')->nullOnDelete();
    29	            $table->text('cancellation_reason')->nullable();
    30	            $table->foreignId('created_by')->nullable()->constrained('users')->nullOnDelete();
    31	            $table->boolean('is_archived')->default(false);
    32	            $table->timestamps();
    33	            $table->softDeletes();
    34	
    35	            $table->index(['facility_id', 'booking_date']);
    36	            $table->index(['member_id', 'booking_date']);
    37	            $table->index('status');
    38	        });
    39	    }
    40	
    41	    public function down(): void
    42	    {
    43	        Schema::dropIfExists('bookings');
    44	    }
    45	};│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [419]: database/migrations/2024_01_01_000022_create_bookings_table.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [420/494]: database/migrations/2024_01_01_000023_create_facilities_table.php
│ LANGUAGE: php | LINES: 41 | SIZE: 1741 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	use Illuminate\Database\Migrations\Migration;
     4	use Illuminate\Database\Schema\Blueprint;
     5	use Illuminate\Support\Facades\Schema;
     6	
     7	return new class extends Migration
     8	{
     9	    public function up(): void
    10	    {
    11	        Schema::create('facilities', function (Blueprint $table) {
    12	            $table->id();
    13	            $table->string('name_ar');
    14	            $table->string('name_en')->nullable();
    15	            $table->string('code', 20)->unique();
    16	            $table->string('facility_type', 30)->comment('court, pool, hall, gym, etc.');
    17	            $table->text('description_ar')->nullable();
    18	            $table->string('location')->nullable();
    19	            $table->integer('capacity')->nullable();
    20	            $table->decimal('hourly_rate', 12, 2)->default(0);
    21	            $table->decimal('half_day_rate', 12, 2)->default(0);
    22	            $table->decimal('full_day_rate', 12, 2)->default(0);
    23	            $table->json('operating_hours')->nullable()->comment('{"sun": {"open":"08:00","close":"22:00"}, ...}');
    24	            $table->json('blocked_dates')->nullable();
    25	            $table->integer('max_booking_hours')->nullable();
    26	            $table->integer('min_booking_hours')->nullable()->default(1);
    27	            $table->integer('advance_booking_days')->nullable()->default(7);
    28	            $table->boolean('requires_approval')->default(false);
    29	            $table->string('photo_path')->nullable();
    30	            $table->integer('sort_order')->default(0);
    31	            $table->boolean('is_active')->default(true);
    32	            $table->boolean('is_archived')->default(false);
    33	            $table->timestamps();
    34	            $table->softDeletes();
    35	        });
    36	    }
    37	
    38	    public function down(): void
    39	    {
    40	        Schema::dropIfExists('facilities');
    41	    }
    42	};│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [420]: database/migrations/2024_01_01_000023_create_facilities_table.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [421/494]: database/migrations/2024_01_01_000024_create_payment_methods_table.php
│ LANGUAGE: php | LINES: 26 | SIZE: 768 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	use Illuminate\Database\Migrations\Migration;
     4	use Illuminate\Database\Schema\Blueprint;
     5	use Illuminate\Support\Facades\Schema;
     6	
     7	return new class extends Migration
     8	{
     9	    public function up(): void
    10	    {
    11	        Schema::create('payment_methods', function (Blueprint $table) {
    12	            $table->id();
    13	            $table->string('name_ar');
    14	            $table->string('name_en')->nullable();
    15	            $table->string('code', 20)->unique();
    16	            $table->boolean('is_active')->default(true);
    17	            $table->integer('sort_order')->default(0);
    18	            $table->boolean('is_archived')->default(false);
    19	            $table->timestamps();
    20	        });
    21	    }
    22	
    23	    public function down(): void
    24	    {
    25	        Schema::dropIfExists('payment_methods');
    26	    }
    27	};│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [421]: database/migrations/2024_01_01_000024_create_payment_methods_table.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [422/494]: database/migrations/2024_01_01_000025_create_cash_registers_table.php
│ LANGUAGE: php | LINES: 35 | SIZE: 1372 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	use Illuminate\Database\Migrations\Migration;
     4	use Illuminate\Database\Schema\Blueprint;
     5	use Illuminate\Support\Facades\Schema;
     6	
     7	return new class extends Migration
     8	{
     9	    public function up(): void
    10	    {
    11	        Schema::create('cash_registers', function (Blueprint $table) {
    12	            $table->id();
    13	            $table->string('register_number', 50)->unique();
    14	            $table->string('name_ar');
    15	            $table->string('name_en')->nullable();
    16	            $table->string('status', 20)->default('closed');
    17	            $table->decimal('opening_balance', 12, 2)->default(0);
    18	            $table->decimal('current_balance', 12, 2)->default(0);
    19	            $table->decimal('closing_balance', 12, 2)->nullable();
    20	            $table->timestamp('opened_at')->nullable();
    21	            $table->timestamp('closed_at')->nullable();
    22	            $table->foreignId('opened_by')->nullable()->constrained('users')->nullOnDelete();
    23	            $table->foreignId('closed_by')->nullable()->constrained('users')->nullOnDelete();
    24	            $table->text('opening_notes')->nullable();
    25	            $table->text('closing_notes')->nullable();
    26	            $table->boolean('is_archived')->default(false);
    27	            $table->timestamps();
    28	            $table->softDeletes();
    29	        });
    30	    }
    31	
    32	    public function down(): void
    33	    {
    34	        Schema::dropIfExists('cash_registers');
    35	    }
    36	};│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [422]: database/migrations/2024_01_01_000025_create_cash_registers_table.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [423/494]: database/migrations/2024_01_01_040001_create_subscription_periods_table.php
│ LANGUAGE: php | LINES: 42 | SIZE: 1815 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	use Illuminate\Database\Migrations\Migration;
     4	use Illuminate\Database\Schema\Blueprint;
     5	use Illuminate\Support\Facades\Schema;
     6	
     7	return new class extends Migration
     8	{
     9	    public function up(): void
    10	    {
    11	        Schema::create('subscription_periods', function (Blueprint $table) {
    12	            $table->id();
    13	            $table->string('name_ar', 100);
    14	            $table->string('name_en', 100)->nullable();
    15	            $table->year('year');
    16	            $table->date('start_date');
    17	            $table->date('end_date');
    18	            $table->date('grace_deadline')->nullable()->comment('Last day to pay without late fee');
    19	            $table->decimal('member_fee', 10, 2)->default(0);
    20	            $table->decimal('spouse_fee', 10, 2)->default(0);
    21	            $table->decimal('child_fee', 10, 2)->default(0);
    22	            $table->decimal('late_fee', 10, 2)->default(0)->comment('Flat late penalty');
    23	            $table->decimal('late_fee_percentage', 5, 2)->default(0)->comment('Percentage late penalty');
    24	            $table->unsignedSmallInteger('grace_period_days')->default(90);
    25	            $table->boolean('is_active')->default(false);
    26	            $table->boolean('is_locked')->default(false)->comment('No more edits allowed');
    27	            $table->boolean('auto_generate')->default(true)->comment('Auto-generate subscriptions for active members');
    28	            $table->text('notes')->nullable();
    29	            $table->foreignId('created_by')->nullable()->constrained('users')->nullOnDelete();
    30	            $table->timestamps();
    31	            $table->softDeletes();
    32	
    33	            $table->unique('year');
    34	            $table->index('is_active');
    35	            $table->index(['start_date', 'end_date']);
    36	        });
    37	    }
    38	
    39	    public function down(): void
    40	    {
    41	        Schema::dropIfExists('subscription_periods');
    42	    }
    43	};│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [423]: database/migrations/2024_01_01_040001_create_subscription_periods_table.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [424/494]: database/migrations/2024_01_01_040002_create_subscriptions_table.php
│ LANGUAGE: php | LINES: 52 | SIZE: 2624 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	use Illuminate\Database\Migrations\Migration;
     4	use Illuminate\Database\Schema\Blueprint;
     5	use Illuminate\Support\Facades\Schema;
     6	
     7	return new class extends Migration
     8	{
     9	    public function up(): void
    10	    {
    11	        Schema::create('subscriptions', function (Blueprint $table) {
    12	            $table->id();
    13	            $table->foreignId('member_id')->constrained('members')->cascadeOnDelete();
    14	            $table->foreignId('subscription_period_id')->constrained('subscription_periods')->cascadeOnDelete();
    15	            $table->string('status', 30)->default('pending')->index();
    16	            $table->decimal('base_amount', 10, 2)->default(0)->comment('Member own fee');
    17	            $table->decimal('dependents_amount', 10, 2)->default(0)->comment('Sum of dependent fees');
    18	            $table->decimal('total_amount', 10, 2)->default(0);
    19	            $table->decimal('discount_amount', 10, 2)->default(0);
    20	            $table->decimal('late_fee_amount', 10, 2)->default(0);
    21	            $table->decimal('net_amount', 10, 2)->default(0)->comment('total - discount + late');
    22	            $table->decimal('paid_amount', 10, 2)->default(0);
    23	            $table->decimal('balance', 10, 2)->default(0)->comment('net - paid');
    24	            $table->string('discount_reason')->nullable();
    25	            $table->foreignId('discount_approved_by')->nullable()->constrained('users')->nullOnDelete();
    26	            $table->date('due_date')->nullable();
    27	            $table->date('paid_date')->nullable();
    28	            $table->date('late_applied_date')->nullable();
    29	            $table->boolean('is_late')->default(false);
    30	            $table->boolean('is_waived')->default(false);
    31	            $table->string('waiver_reason')->nullable();
    32	            $table->foreignId('waived_by')->nullable()->constrained('users')->nullOnDelete();
    33	            $table->date('waived_date')->nullable();
    34	            $table->json('dependents_breakdown')->nullable()->comment('JSON of each dependent fee');
    35	            $table->text('notes')->nullable();
    36	            $table->foreignId('created_by')->nullable()->constrained('users')->nullOnDelete();
    37	            $table->foreignId('updated_by')->nullable()->constrained('users')->nullOnDelete();
    38	            $table->timestamps();
    39	            $table->softDeletes();
    40	
    41	            $table->unique(['member_id', 'subscription_period_id'], 'sub_member_period_unique');
    42	            $table->index('status');
    43	            $table->index('due_date');
    44	            $table->index('is_late');
    45	            $table->index('paid_date');
    46	        });
    47	    }
    48	
    49	    public function down(): void
    50	    {
    51	        Schema::dropIfExists('subscriptions');
    52	    }
    53	};│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [424]: database/migrations/2024_01_01_040002_create_subscriptions_table.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [425/494]: database/migrations/2024_01_01_040003_create_subscription_payments_table.php
│ LANGUAGE: php | LINES: 36 | SIZE: 1416 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	use Illuminate\Database\Migrations\Migration;
     4	use Illuminate\Database\Schema\Blueprint;
     5	use Illuminate\Support\Facades\Schema;
     6	
     7	return new class extends Migration
     8	{
     9	    public function up(): void
    10	    {
    11	        Schema::create('subscription_payments', function (Blueprint $table) {
    12	            $table->id();
    13	            $table->foreignId('subscription_id')->constrained('subscriptions')->cascadeOnDelete();
    14	            $table->foreignId('receipt_id')->nullable()->constrained('receipts')->nullOnDelete();
    15	            $table->decimal('amount', 10, 2);
    16	            $table->date('payment_date');
    17	            $table->string('payment_method', 30)->default('cash');
    18	            $table->string('reference_number')->nullable();
    19	            $table->text('notes')->nullable();
    20	            $table->boolean('is_reversed')->default(false);
    21	            $table->foreignId('reversed_by')->nullable()->constrained('users')->nullOnDelete();
    22	            $table->timestamp('reversed_at')->nullable();
    23	            $table->string('reversal_reason')->nullable();
    24	            $table->foreignId('created_by')->nullable()->constrained('users')->nullOnDelete();
    25	            $table->timestamps();
    26	            $table->softDeletes();
    27	
    28	            $table->index('payment_date');
    29	            $table->index('is_reversed');
    30	        });
    31	    }
    32	
    33	    public function down(): void
    34	    {
    35	        Schema::dropIfExists('subscription_payments');
    36	    }
    37	};│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [425]: database/migrations/2024_01_01_040003_create_subscription_payments_table.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [426/494]: database/migrations/2024_01_01_200001_create_violation_types_table.php
│ LANGUAGE: php | LINES: 29 | SIZE: 985 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	use Illuminate\Database\Migrations\Migration;
     4	use Illuminate\Database\Schema\Blueprint;
     5	use Illuminate\Support\Facades\Schema;
     6	
     7	return new class extends Migration
     8	{
     9	    public function up(): void
    10	    {
    11	        Schema::create('violation_types', function (Blueprint $table) {
    12	            $table->id();
    13	            $table->string('code', 50)->unique();
    14	            $table->string('name_ar', 255);
    15	            $table->string('name_en', 255)->nullable();
    16	            $table->text('description')->nullable();
    17	            $table->string('default_severity', 30)->default('minor');
    18	            $table->boolean('requires_investigation')->default(false);
    19	            $table->boolean('requires_board_review')->default(false);
    20	            $table->boolean('is_active')->default(true);
    21	            $table->integer('display_order')->default(0);
    22	            $table->timestamps();
    23	        });
    24	    }
    25	
    26	    public function down(): void
    27	    {
    28	        Schema::dropIfExists('violation_types');
    29	    }
    30	};│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [426]: database/migrations/2024_01_01_200001_create_violation_types_table.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [427/494]: database/migrations/2024_01_01_200002_create_penalty_types_table.php
│ LANGUAGE: php | LINES: 30 | SIZE: 1049 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	use Illuminate\Database\Migrations\Migration;
     4	use Illuminate\Database\Schema\Blueprint;
     5	use Illuminate\Support\Facades\Schema;
     6	
     7	return new class extends Migration
     8	{
     9	    public function up(): void
    10	    {
    11	        Schema::create('penalty_types', function (Blueprint $table) {
    12	            $table->id();
    13	            $table->string('code', 50)->unique();
    14	            $table->string('name_ar', 255);
    15	            $table->string('name_en', 255)->nullable();
    16	            $table->text('description')->nullable();
    17	            $table->decimal('default_fine_amount', 10, 2)->default(0);
    18	            $table->boolean('requires_suspension')->default(false);
    19	            $table->integer('default_suspension_days')->default(0);
    20	            $table->boolean('requires_board_approval')->default(false);
    21	            $table->boolean('is_active')->default(true);
    22	            $table->integer('display_order')->default(0);
    23	            $table->timestamps();
    24	        });
    25	    }
    26	
    27	    public function down(): void
    28	    {
    29	        Schema::dropIfExists('penalty_types');
    30	    }
    31	};│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [427]: database/migrations/2024_01_01_200002_create_penalty_types_table.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [428/494]: database/migrations/2024_01_01_200003_create_violations_table.php
│ LANGUAGE: php | LINES: 68 | SIZE: 2784 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	use Illuminate\Database\Migrations\Migration;
     4	use Illuminate\Database\Schema\Blueprint;
     5	use Illuminate\Support\Facades\Schema;
     6	
     7	return new class extends Migration
     8	{
     9	    public function up(): void
    10	    {
    11	        Schema::create('violations', function (Blueprint $table) {
    12	            $table->id();
    13	            $table->string('violation_number', 50)->unique();
    14	            $table->foreignId('member_id')->constrained('members')->cascadeOnDelete();
    15	            $table->foreignId('violation_type_id')->nullable()->constrained('violation_types')->nullOnDelete();
    16	            $table->string('severity', 30)->default('minor');
    17	            $table->string('status', 30)->default('reported');
    18	            $table->string('title', 255);
    19	            $table->text('description')->nullable();
    20	            $table->dateTime('violation_date');
    21	            $table->string('violation_location', 255)->nullable();
    22	
    23	            // Reporting
    24	            $table->foreignId('reported_by')->nullable()->constrained('users')->nullOnDelete();
    25	            $table->dateTime('reported_at')->nullable();
    26	            $table->text('witnesses')->nullable();
    27	            $table->text('evidence_notes')->nullable();
    28	            $table->text('notes')->nullable();
    29	
    30	            // Investigation
    31	            $table->foreignId('investigated_by')->nullable()->constrained('users')->nullOnDelete();
    32	            $table->dateTime('investigation_started_at')->nullable();
    33	            $table->dateTime('investigation_completed_at')->nullable();
    34	            $table->text('investigation_notes')->nullable();
    35	            $table->text('investigation_findings')->nullable();
    36	
    37	            // Board escalation
    38	            $table->boolean('escalated_to_board')->default(false);
    39	            $table->dateTime('escalation_date')->nullable();
    40	            $table->text('escalation_reason')->nullable();
    41	            $table->date('board_decision_date')->nullable();
    42	            $table->string('board_decision_reference', 100)->nullable();
    43	            $table->text('board_decision_notes')->nullable();
    44	
    45	            // Dismissal
    46	            $table->foreignId('dismissed_by')->nullable()->constrained('users')->nullOnDelete();
    47	            $table->dateTime('dismissed_at')->nullable();
    48	            $table->text('dismissal_reason')->nullable();
    49	
    50	            // Resolution
    51	            $table->dateTime('resolved_at')->nullable();
    52	
    53	            $table->softDeletes();
    54	            $table->timestamps();
    55	
    56	            // Indexes
    57	            $table->index('member_id');
    58	            $table->index('status');
    59	            $table->index('severity');
    60	            $table->index('violation_date');
    61	            $table->index(['member_id', 'status']);
    62	        });
    63	    }
    64	
    65	    public function down(): void
    66	    {
    67	        Schema::dropIfExists('violations');
    68	    }
    69	};│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [428]: database/migrations/2024_01_01_200003_create_violations_table.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [429/494]: database/migrations/2024_01_01_200004_create_penalties_table.php
│ LANGUAGE: php | LINES: 68 | SIZE: 2600 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	use Illuminate\Database\Migrations\Migration;
     4	use Illuminate\Database\Schema\Blueprint;
     5	use Illuminate\Support\Facades\Schema;
     6	
     7	return new class extends Migration
     8	{
     9	    public function up(): void
    10	    {
    11	        Schema::create('penalties', function (Blueprint $table) {
    12	            $table->id();
    13	            $table->string('penalty_number', 50)->unique();
    14	            $table->foreignId('violation_id')->constrained('violations')->cascadeOnDelete();
    15	            $table->foreignId('member_id')->constrained('members')->cascadeOnDelete();
    16	            $table->foreignId('penalty_type_id')->nullable()->constrained('penalty_types')->nullOnDelete();
    17	            $table->string('status', 30)->default('active');
    18	            $table->text('description')->nullable();
    19	
    20	            // Dates
    21	            $table->date('imposed_date');
    22	            $table->date('effective_from');
    23	            $table->date('effective_until')->nullable();
    24	
    25	            // Fine
    26	            $table->decimal('fine_amount', 10, 2)->default(0);
    27	            $table->boolean('fine_paid')->default(false);
    28	            $table->dateTime('fine_paid_at')->nullable();
    29	            $table->foreignId('fine_receipt_id')->nullable()->constrained('receipts')->nullOnDelete();
    30	
    31	            // Suspension
    32	            $table->boolean('requires_suspension')->default(false);
    33	            $table->integer('suspension_days')->nullable();
    34	
    35	            // Admin
    36	            $table->foreignId('imposed_by')->nullable()->constrained('users')->nullOnDelete();
    37	            $table->string('board_decision_reference', 100)->nullable();
    38	            $table->text('notes')->nullable();
    39	
    40	            // Completion
    41	            $table->dateTime('completed_at')->nullable();
    42	
    43	            // Waiver
    44	            $table->foreignId('waived_by')->nullable()->constrained('users')->nullOnDelete();
    45	            $table->dateTime('waived_at')->nullable();
    46	            $table->text('waive_reason')->nullable();
    47	
    48	            // Overturn
    49	            $table->foreignId('overturned_by')->nullable()->constrained('users')->nullOnDelete();
    50	            $table->dateTime('overturned_at')->nullable();
    51	            $table->text('overturn_reason')->nullable();
    52	
    53	            $table->softDeletes();
    54	            $table->timestamps();
    55	
    56	            // Indexes
    57	            $table->index('member_id');
    58	            $table->index('violation_id');
    59	            $table->index('status');
    60	            $table->index(['member_id', 'status']);
    61	            $table->index(['status', 'fine_paid']);
    62	        });
    63	    }
    64	
    65	    public function down(): void
    66	    {
    67	        Schema::dropIfExists('penalties');
    68	    }
    69	};│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [429]: database/migrations/2024_01_01_200004_create_penalties_table.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [430/494]: database/migrations/2024_01_01_200005_create_member_suspensions_table.php
│ LANGUAGE: php | LINES: 51 | SIZE: 1945 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	use Illuminate\Database\Migrations\Migration;
     4	use Illuminate\Database\Schema\Blueprint;
     5	use Illuminate\Support\Facades\Schema;
     6	
     7	return new class extends Migration
     8	{
     9	    public function up(): void
    10	    {
    11	        Schema::create('member_suspensions', function (Blueprint $table) {
    12	            $table->id();
    13	            $table->foreignId('member_id')->constrained('members')->cascadeOnDelete();
    14	            $table->foreignId('penalty_id')->nullable()->constrained('penalties')->nullOnDelete();
    15	            $table->foreignId('violation_id')->nullable()->constrained('violations')->nullOnDelete();
    16	            $table->string('status', 30)->default('active');
    17	            $table->date('start_date');
    18	            $table->date('end_date')->nullable();
    19	            $table->date('actual_end_date')->nullable();
    20	            $table->text('reason')->nullable();
    21	            $table->string('board_decision_reference', 100)->nullable();
    22	
    23	            // Admin
    24	            $table->foreignId('suspended_by')->nullable()->constrained('users')->nullOnDelete();
    25	            $table->text('notes')->nullable();
    26	
    27	            // Lift
    28	            $table->foreignId('lifted_by')->nullable()->constrained('users')->nullOnDelete();
    29	            $table->dateTime('lifted_at')->nullable();
    30	            $table->text('lift_reason')->nullable();
    31	
    32	            // Extension
    33	            $table->foreignId('extended_by')->nullable()->constrained('users')->nullOnDelete();
    34	            $table->dateTime('extended_at')->nullable();
    35	            $table->text('extension_reason')->nullable();
    36	
    37	            $table->softDeletes();
    38	            $table->timestamps();
    39	
    40	            // Indexes
    41	            $table->index('member_id');
    42	            $table->index('status');
    43	            $table->index(['member_id', 'status']);
    44	            $table->index(['status', 'end_date']);
    45	        });
    46	    }
    47	
    48	    public function down(): void
    49	    {
    50	        Schema::dropIfExists('member_suspensions');
    51	    }
    52	};│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [430]: database/migrations/2024_01_01_200005_create_member_suspensions_table.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [431/494]: database/migrations/2024_01_01_200006_create_violation_documents_table.php
│ LANGUAGE: php | LINES: 27 | SIZE: 791 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	use Illuminate\Database\Migrations\Migration;
     4	use Illuminate\Database\Schema\Blueprint;
     5	use Illuminate\Support\Facades\Schema;
     6	
     7	return new class extends Migration
     8	{
     9	    public function up(): void
    10	    {
    11	        Schema::create('violation_documents', function (Blueprint $table) {
    12	            $table->id();
    13	            $table->foreignId('violation_id')->constrained('violations')->cascadeOnDelete();
    14	            $table->string('title', 255);
    15	            $table->string('document_type', 50);
    16	            $table->string('file_path', 500);
    17	            $table->text('description')->nullable();
    18	            $table->timestamps();
    19	
    20	            $table->index('violation_id');
    21	        });
    22	    }
    23	
    24	    public function down(): void
    25	    {
    26	        Schema::dropIfExists('violation_documents');
    27	    }
    28	};│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [431]: database/migrations/2024_01_01_200006_create_violation_documents_table.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [432/494]: database/migrations/2024_01_15_000001_create_cash_register_transactions_table.php
│ LANGUAGE: php | LINES: 31 | SIZE: 1056 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	use Illuminate\Database\Migrations\Migration;
     4	use Illuminate\Database\Schema\Blueprint;
     5	use Illuminate\Support\Facades\Schema;
     6	
     7	return new class extends Migration
     8	{
     9	    public function up(): void
    10	    {
    11	        Schema::create('cash_register_transactions', function (Blueprint $table) {
    12	            $table->id();
    13	            $table->foreignId('cash_register_id')->constrained()->cascadeOnDelete();
    14	            $table->enum('type', ['in', 'out']);
    15	            $table->decimal('amount', 12, 2);
    16	            $table->decimal('balance_before', 12, 2);
    17	            $table->decimal('balance_after', 12, 2);
    18	            $table->string('description', 500);
    19	            $table->nullableMorphs('reference');
    20	            $table->foreignId('created_by')->nullable()->constrained('users')->nullOnDelete();
    21	            $table->timestamps();
    22	
    23	            $table->index(['cash_register_id', 'created_at']);
    24	            $table->index('type');
    25	        });
    26	    }
    27	
    28	    public function down(): void
    29	    {
    30	        Schema::dropIfExists('cash_register_transactions');
    31	    }
    32	};│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [432]: database/migrations/2024_01_15_000001_create_cash_register_transactions_table.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [433/494]: database/migrations/2024_01_15_000002_create_club_notifications_table.php
│ LANGUAGE: php | LINES: 37 | SIZE: 1279 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	use Illuminate\Database\Migrations\Migration;
     4	use Illuminate\Database\Schema\Blueprint;
     5	use Illuminate\Support\Facades\Schema;
     6	
     7	return new class extends Migration
     8	{
     9	    public function up(): void
    10	    {
    11	        Schema::create('club_notifications', function (Blueprint $table) {
    12	            $table->id();
    13	            $table->morphs('notifiable');
    14	            $table->string('channel', 50)->default('system');
    15	            $table->string('priority', 20)->default('normal');
    16	            $table->string('title_ar', 500);
    17	            $table->string('title_en', 500)->nullable();
    18	            $table->text('body_ar');
    19	            $table->text('body_en')->nullable();
    20	            $table->string('action_url', 500)->nullable();
    21	            $table->string('category', 100)->nullable();
    22	            $table->json('data')->nullable();
    23	            $table->timestamp('sent_at')->nullable();
    24	            $table->timestamp('read_at')->nullable();
    25	            $table->timestamps();
    26	
    27	            $table->index(['notifiable_type', 'notifiable_id', 'read_at']);
    28	            $table->index('channel');
    29	            $table->index('category');
    30	            $table->index('sent_at');
    31	        });
    32	    }
    33	
    34	    public function down(): void
    35	    {
    36	        Schema::dropIfExists('club_notifications');
    37	    }
    38	};│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [433]: database/migrations/2024_01_15_000002_create_club_notifications_table.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [434/494]: database/migrations/2024_01_15_000002_create_receipt_items_table.php
│ LANGUAGE: php | LINES: 30 | SIZE: 953 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	use Illuminate\Database\Migrations\Migration;
     4	use Illuminate\Database\Schema\Blueprint;
     5	use Illuminate\Support\Facades\Schema;
     6	
     7	return new class extends Migration
     8	{
     9	    public function up(): void
    10	    {
    11	        Schema::create('receipt_items', function (Blueprint $table) {
    12	            $table->id();
    13	            $table->foreignId('receipt_id')->constrained()->cascadeOnDelete();
    14	            $table->string('fee_category', 50);
    15	            $table->string('description_ar', 500);
    16	            $table->string('description_en', 500)->nullable();
    17	            $table->decimal('amount', 12, 2);
    18	            $table->unsignedInteger('quantity')->default(1);
    19	            $table->decimal('subtotal', 12, 2);
    20	            $table->unsignedInteger('sort_order')->default(0);
    21	            $table->timestamps();
    22	
    23	            $table->index('receipt_id');
    24	        });
    25	    }
    26	
    27	    public function down(): void
    28	    {
    29	        Schema::dropIfExists('receipt_items');
    30	    }
    31	};│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [434]: database/migrations/2024_01_15_000002_create_receipt_items_table.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [435/494]: database/migrations/2024_01_15_000003_add_admin_fields_to_users_table.php
│ LANGUAGE: php | LINES: 35 | SIZE: 1291 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	use Illuminate\Database\Migrations\Migration;
     4	use Illuminate\Database\Schema\Blueprint;
     5	use Illuminate\Support\Facades\Schema;
     6	
     7	return new class extends Migration
     8	{
     9	    public function up(): void
    10	    {
    11	        Schema::table('users', function (Blueprint $table) {
    12	            if (!Schema::hasColumn('users', 'phone')) {
    13	                $table->string('phone', 20)->nullable()->after('email');
    14	            }
    15	            if (!Schema::hasColumn('users', 'job_title')) {
    16	                $table->string('job_title', 255)->nullable()->after('phone');
    17	            }
    18	            if (!Schema::hasColumn('users', 'is_active')) {
    19	                $table->boolean('is_active')->default(true)->after('job_title');
    20	            }
    21	            if (!Schema::hasColumn('users', 'last_login_at')) {
    22	                $table->timestamp('last_login_at')->nullable()->after('is_active');
    23	            }
    24	            if (!Schema::hasColumn('users', 'last_login_ip')) {
    25	                $table->string('last_login_ip', 45)->nullable()->after('last_login_at');
    26	            }
    27	        });
    28	    }
    29	
    30	    public function down(): void
    31	    {
    32	        Schema::table('users', function (Blueprint $table) {
    33	            $table->dropColumn(['phone', 'job_title', 'is_active', 'last_login_at', 'last_login_ip']);
    34	        });
    35	    }
    36	};│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [435]: database/migrations/2024_01_15_000003_add_admin_fields_to_users_table.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [436/494]: database/seeders/DatabaseSeeder.php
│ LANGUAGE: php | LINES: 36 | SIZE: 989 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace Database\Seeders;
     4	
     5	use Illuminate\Database\Seeder;
     6	
     7	class DatabaseSeeder extends Seeder
     8	{
     9	    public function run(): void
    10	    {
    11	        $this->call([
    12	            // — Lookup tables (order matters: no FK dependencies) —
    13	            NationalitySeeder::class,
    14	            ReligionSeeder::class,
    15	            MaritalStatusSeeder::class,
    16	            GovernorateSeeder::class,
    17	            EducationalQualificationSeeder::class,
    18	            DocumentTypeSeeder::class,
    19	            PaymentMethodSeeder::class,
    20	
    21	            // — Core domain —
    22	            MembershipTypeSeeder::class,
    23	            MembershipStatusSeeder::class,
    24	            FeeStructureSeeder::class,
    25	
    26	            // — Disciplinary —
    27	            ViolationTypeSeeder::class,
    28	            PenaltyTypeSeeder::class,
    29	
    30	            // — Auth & Permissions —
    31	            RolesAndPermissionsSeeder::class,
    32	
    33	            // — System Configuration —
    34	            SystemSettingsSeeder::class,
    35	        ]);
    36	    }
    37	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [436]: database/seeders/DatabaseSeeder.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [437/494]: database/seeders/DocumentTypeSeeder.php
│ LANGUAGE: php | LINES: 141 | SIZE: 6261 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace Database\Seeders;
     4	
     5	use App\Models\DocumentType;
     6	use Illuminate\Database\Seeder;
     7	
     8	class DocumentTypeSeeder extends Seeder
     9	{
    10	    public function run(): void
    11	    {
    12	        $types = [
    13	            [
    14	                'name_ar' => 'بطاقة الرقم القومي',
    15	                'name_en' => 'National ID Card',
    16	                'code' => 'NATIONAL_ID',
    17	                'description_ar' => 'صورة بطاقة الرقم القومي (وجه وظهر)',
    18	                'is_required_for_application' => true,
    19	                'is_required_for_activation' => true,
    20	                'is_required_for_dependent' => true,
    21	                'allowed_extensions' => 'pdf,jpg,jpeg,png',
    22	                'max_file_size_kb' => 5120,
    23	                'sort_order' => 1,
    24	            ],
    25	            [
    26	                'name_ar' => 'صورة شخصية',
    27	                'name_en' => 'Personal Photo',
    28	                'code' => 'PERSONAL_PHOTO',
    29	                'description_ar' => 'صورة شخصية حديثة بخلفية بيضاء',
    30	                'is_required_for_application' => true,
    31	                'is_required_for_activation' => true,
    32	                'is_required_for_dependent' => true,
    33	                'allowed_extensions' => 'jpg,jpeg,png',
    34	                'max_file_size_kb' => 3072,
    35	                'sort_order' => 2,
    36	            ],
    37	            [
    38	                'name_ar' => 'شهادة الميلاد',
    39	                'name_en' => 'Birth Certificate',
    40	                'code' => 'BIRTH_CERTIFICATE',
    41	                'description_ar' => 'شهادة الميلاد أو مستخرج رسمي',
    42	                'is_required_for_application' => false,
    43	                'is_required_for_activation' => false,
    44	                'is_required_for_dependent' => true,
    45	                'allowed_extensions' => 'pdf,jpg,jpeg,png',
    46	                'max_file_size_kb' => 5120,
    47	                'sort_order' => 3,
    48	            ],
    49	            [
    50	                'name_ar' => 'قسيمة الزواج',
    51	                'name_en' => 'Marriage Certificate',
    52	                'code' => 'MARRIAGE_CERTIFICATE',
    53	                'description_ar' => 'قسيمة الزواج الرسمية',
    54	                'is_required_for_application' => false,
    55	                'is_required_for_activation' => false,
    56	                'is_required_for_dependent' => false,
    57	                'allowed_extensions' => 'pdf,jpg,jpeg,png',
    58	                'max_file_size_kb' => 5120,
    59	                'sort_order' => 4,
    60	            ],
    61	            [
    62	                'name_ar' => 'شهادة الوفاة',
    63	                'name_en' => 'Death Certificate',
    64	                'code' => 'DEATH_CERTIFICATE',
    65	                'description_ar' => 'شهادة الوفاة الرسمية (لحالات نقل العضوية)',
    66	                'is_required_for_application' => false,
    67	                'is_required_for_activation' => false,
    68	                'is_required_for_dependent' => false,
    69	                'allowed_extensions' => 'pdf,jpg,jpeg,png',
    70	                'max_file_size_kb' => 5120,
    71	                'sort_order' => 5,
    72	            ],
    73	            [
    74	                'name_ar' => 'إشهاد الطلاق',
    75	                'name_en' => 'Divorce Certificate',
    76	                'code' => 'DIVORCE_CERTIFICATE',
    77	                'description_ar' => 'إشهاد الطلاق الرسمي (لحالات العضوية المكتسبة)',
    78	                'is_required_for_application' => false,
    79	                'is_required_for_activation' => false,
    80	                'is_required_for_dependent' => false,
    81	                'allowed_extensions' => 'pdf,jpg,jpeg,png',
    82	                'max_file_size_kb' => 5120,
    83	                'sort_order' => 6,
    84	            ],
    85	            [
    86	                'name_ar' => 'إثبات سكن / إيصال مرافق',
    87	                'name_en' => 'Proof of Address',
    88	                'code' => 'PROOF_OF_ADDRESS',
    89	                'description_ar' => 'إيصال مرافق حديث أو عقد إيجار',
    90	                'is_required_for_application' => false,
    91	                'is_required_for_activation' => false,
    92	                'is_required_for_dependent' => false,
    93	                'allowed_extensions' => 'pdf,jpg,jpeg,png',
    94	                'max_file_size_kb' => 5120,
    95	                'sort_order' => 7,
    96	            ],
    97	            [
    98	                'name_ar' => 'خطاب جهة العمل',
    99	                'name_en' => 'Employment Letter',
   100	                'code' => 'EMPLOYMENT_LETTER',
   101	                'description_ar' => 'خطاب من جهة العمل يفيد الوظيفة والراتب',
   102	                'is_required_for_application' => false,
   103	                'is_required_for_activation' => false,
   104	                'is_required_for_dependent' => false,
   105	                'allowed_extensions' => 'pdf,jpg,jpeg,png',
   106	                'max_file_size_kb' => 5120,
   107	                'sort_order' => 8,
   108	            ],
   109	            [
   110	                'name_ar' => 'شهادة رياضية',
   111	                'name_en' => 'Athletic Certificate',
   112	                'code' => 'ATHLETIC_CERTIFICATE',
   113	                'description_ar' => 'شهادة إثبات ممارسة رياضية لمدة 8 سنوات أو أكثر',
   114	                'is_required_for_application' => false,
   115	                'is_required_for_activation' => false,
   116	                'is_required_for_dependent' => false,
   117	                'allowed_extensions' => 'pdf,jpg,jpeg,png',
   118	                'max_file_size_kb' => 5120,
   119	                'sort_order' => 9,
   120	            ],
   121	            [
   122	                'name_ar' => 'مستند آخر',
   123	                'name_en' => 'Other Document',
   124	                'code' => 'OTHER',
   125	                'description_ar' => 'أي مستند إضافي مطلوب',
   126	                'is_required_for_application' => false,
   127	                'is_required_for_activation' => false,
   128	                'is_required_for_dependent' => false,
   129	                'allowed_extensions' => 'pdf,jpg,jpeg,png,doc,docx',
   130	                'max_file_size_kb' => 10240,
   131	                'sort_order' => 99,
   132	            ],
   133	        ];
   134	
   135	        foreach ($types as $type) {
   136	            DocumentType::updateOrCreate(
   137	                ['code' => $type['code']],
   138	                $type,
   139	            );
   140	        }
   141	    }
   142	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [437]: database/seeders/DocumentTypeSeeder.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [438/494]: database/seeders/EducationalQualificationSeeder.php
│ LANGUAGE: php | LINES: 32 | SIZE: 1553 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace Database\Seeders;
     4	
     5	use App\Models\EducationalQualification;
     6	use Illuminate\Database\Seeder;
     7	
     8	class EducationalQualificationSeeder extends Seeder
     9	{
    10	    public function run(): void
    11	    {
    12	        $qualifications = [
    13	            ['name_ar' => 'بدون مؤهل', 'name_en' => 'No Qualification', 'sort_order' => 1],
    14	            ['name_ar' => 'تعليم أساسي', 'name_en' => 'Basic Education', 'sort_order' => 2],
    15	            ['name_ar' => 'ثانوي عام', 'name_en' => 'General Secondary', 'sort_order' => 3],
    16	            ['name_ar' => 'دبلوم فني', 'name_en' => 'Technical Diploma', 'sort_order' => 4],
    17	            ['name_ar' => 'معهد فوق متوسط', 'name_en' => 'Higher Institute', 'sort_order' => 5],
    18	            ['name_ar' => 'بكالوريوس', 'name_en' => 'Bachelor\'s Degree', 'sort_order' => 6],
    19	            ['name_ar' => 'ليسانس', 'name_en' => 'License', 'sort_order' => 7],
    20	            ['name_ar' => 'دبلوم دراسات عليا', 'name_en' => 'Postgraduate Diploma', 'sort_order' => 8],
    21	            ['name_ar' => 'ماجستير', 'name_en' => 'Master\'s Degree', 'sort_order' => 9],
    22	            ['name_ar' => 'دكتوراه', 'name_en' => 'Doctorate', 'sort_order' => 10],
    23	            ['name_ar' => 'أستاذ جامعي', 'name_en' => 'Professor', 'sort_order' => 11],
    24	        ];
    25	
    26	        foreach ($qualifications as $qual) {
    27	            EducationalQualification::updateOrCreate(
    28	                ['name_ar' => $qual['name_ar']],
    29	                $qual,
    30	            );
    31	        }
    32	    }
    33	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [438]: database/seeders/EducationalQualificationSeeder.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [439/494]: database/seeders/FeeStructureSeeder.php
│ LANGUAGE: php | LINES: 148 | SIZE: 5702 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace Database\Seeders;
     4	
     5	use App\Models\FeeStructure;
     6	use App\Models\MembershipType;
     7	use Illuminate\Database\Seeder;
     8	
     9	class FeeStructureSeeder extends Seeder
    10	{
    11	    public function run(): void
    12	    {
    13	        $types = MembershipType::pluck('id', 'code');
    14	
    15	        $fees = [
    16	            // Working membership fees
    17	            [
    18	                'name_ar' => 'رسم طلب — عضوية عاملة',
    19	                'name_en' => 'Application Fee — Working',
    20	                'code' => 'APP_FEE_WORKING',
    21	                'membership_type_id' => $types['WORKING'] ?? null,
    22	                'fee_category' => 'application',
    23	                'applies_to' => 'member',
    24	                'amount' => 5000.00,
    25	            ],
    26	            [
    27	                'name_ar' => 'قيمة عضوية — عاملة',
    28	                'name_en' => 'Membership Value — Working',
    29	                'code' => 'MEM_VAL_WORKING',
    30	                'membership_type_id' => $types['WORKING'] ?? null,
    31	                'fee_category' => 'membership_value',
    32	                'applies_to' => 'member',
    33	                'amount' => 50000.00,
    34	            ],
    35	            [
    36	                'name_ar' => 'اشتراك سنوي — عضوية عاملة',
    37	                'name_en' => 'Annual Subscription — Working',
    38	                'code' => 'ANN_SUB_WORKING',
    39	                'membership_type_id' => $types['WORKING'] ?? null,
    40	                'fee_category' => 'subscription',
    41	                'applies_to' => 'member',
    42	                'amount' => 5000.00,
    43	            ],
    44	
    45	            // Dependent fees — spouse
    46	            [
    47	                'name_ar' => 'إضافة زوج/زوجة — عاملة',
    48	                'name_en' => 'Spouse Addition — Working',
    49	                'code' => 'DEP_SPOUSE_WORKING',
    50	                'membership_type_id' => $types['WORKING'] ?? null,
    51	                'fee_category' => 'dependent',
    52	                'applies_to' => 'dependent',
    53	                'amount' => 2000.00,
    54	                'dependent_relation' => 'spouse',
    55	            ],
    56	
    57	            // Dependent fees — child under 16
    58	            [
    59	                'name_ar' => 'إضافة ابن/ابنة أقل من 16 — عاملة',
    60	                'name_en' => 'Child Under 16 — Working',
    61	                'code' => 'DEP_CHILD_U16_WORKING',
    62	                'membership_type_id' => $types['WORKING'] ?? null,
    63	                'fee_category' => 'dependent',
    64	                'applies_to' => 'dependent',
    65	                'amount' => 1000.00,
    66	                'dependent_relation' => 'child',
    67	                'age_min' => 0,
    68	                'age_max' => 15,
    69	            ],
    70	
    71	            // Dependent fees — child 16-21
    72	            [
    73	                'name_ar' => 'إضافة ابن/ابنة من 16 إلى 21 — عاملة',
    74	                'name_en' => 'Child 16-21 — Working',
    75	                'code' => 'DEP_CHILD_16_21_WORKING',
    76	                'membership_type_id' => $types['WORKING'] ?? null,
    77	                'fee_category' => 'dependent',
    78	                'applies_to' => 'dependent',
    79	                'amount' => 1500.00,
    80	                'dependent_relation' => 'child',
    81	                'age_min' => 16,
    82	                'age_max' => 21,
    83	            ],
    84	
    85	            // Dependent annual subscription
    86	            [
    87	                'name_ar' => 'اشتراك سنوي — تابع (زوج/زوجة)',
    88	                'name_en' => 'Annual Sub — Dependent Spouse',
    89	                'code' => 'ANN_SUB_DEP_SPOUSE',
    90	                'membership_type_id' => null,
    91	                'fee_category' => 'dependent_subscription',
    92	                'applies_to' => 'dependent',
    93	                'amount' => 1500.00,
    94	                'dependent_relation' => 'spouse',
    95	            ],
    96	            [
    97	                'name_ar' => 'اشتراك سنوي — تابع (ابن/ابنة)',
    98	                'name_en' => 'Annual Sub — Dependent Child',
    99	                'code' => 'ANN_SUB_DEP_CHILD',
   100	                'membership_type_id' => null,
   101	                'fee_category' => 'dependent_subscription',
   102	                'applies_to' => 'dependent',
   103	                'amount' => 1000.00,
   104	                'dependent_relation' => 'child',
   105	            ],
   106	
   107	            // Transfer fees
   108	            [
   109	                'name_ar' => 'رسم نقل عضوية — وفاة',
   110	                'name_en' => 'Transfer Fee — Death',
   111	                'code' => 'TRANSFER_DEATH',
   112	                'membership_type_id' => null,
   113	                'fee_category' => 'transfer',
   114	                'applies_to' => 'member',
   115	                'amount' => 10000.00,
   116	            ],
   117	            [
   118	                'name_ar' => 'رسم نقل عضوية — طلاق (مكتسبة)',
   119	                'name_en' => 'Transfer Fee — Divorce',
   120	                'code' => 'TRANSFER_DIVORCE',
   121	                'membership_type_id' => null,
   122	                'fee_category' => 'transfer',
   123	                'applies_to' => 'member',
   124	                'amount' => 15000.00,
   125	            ],
   126	
   127	            // Card reprint fee
   128	            [
   129	                'name_ar' => 'رسم إعادة طباعة كارنيه',
   130	                'name_en' => 'Card Reprint Fee',
   131	                'code' => 'CARD_REPRINT',
   132	                'membership_type_id' => null,
   133	                'fee_category' => 'card',
   134	                'applies_to' => 'both',
   135	                'amount' => 100.00,
   136	            ],
   137	        ];
   138	
   139	        foreach ($fees as $fee) {
   140	            FeeStructure::updateOrCreate(
   141	                ['code' => $fee['code']],
   142	                array_merge($fee, [
   143	                    'is_active' => true,
   144	                    'effective_from' => now()->startOfYear()->toDateString(),
   145	                ]),
   146	            );
   147	        }
   148	    }
   149	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [439]: database/seeders/FeeStructureSeeder.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [440/494]: database/seeders/GovernorateSeeder.php
│ LANGUAGE: php | LINES: 48 | SIZE: 2793 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace Database\Seeders;
     4	
     5	use App\Models\Governorate;
     6	use Illuminate\Database\Seeder;
     7	
     8	class GovernorateSeeder extends Seeder
     9	{
    10	    public function run(): void
    11	    {
    12	        $governorates = [
    13	            ['name_ar' => 'القاهرة', 'name_en' => 'Cairo', 'code' => 'CAI'],
    14	            ['name_ar' => 'الجيزة', 'name_en' => 'Giza', 'code' => 'GIZ'],
    15	            ['name_ar' => 'الإسكندرية', 'name_en' => 'Alexandria', 'code' => 'ALX'],
    16	            ['name_ar' => 'الدقهلية', 'name_en' => 'Dakahlia', 'code' => 'DKH'],
    17	            ['name_ar' => 'البحر الأحمر', 'name_en' => 'Red Sea', 'code' => 'RDS'],
    18	            ['name_ar' => 'البحيرة', 'name_en' => 'Beheira', 'code' => 'BHR'],
    19	            ['name_ar' => 'الفيوم', 'name_en' => 'Fayoum', 'code' => 'FYM'],
    20	            ['name_ar' => 'الغربية', 'name_en' => 'Gharbia', 'code' => 'GHR'],
    21	            ['name_ar' => 'الإسماعيلية', 'name_en' => 'Ismailia', 'code' => 'ISM'],
    22	            ['name_ar' => 'المنوفية', 'name_en' => 'Menofia', 'code' => 'MNF'],
    23	            ['name_ar' => 'المنيا', 'name_en' => 'Minya', 'code' => 'MNY'],
    24	            ['name_ar' => 'القليوبية', 'name_en' => 'Qalyubia', 'code' => 'QLB'],
    25	            ['name_ar' => 'الوادي الجديد', 'name_en' => 'New Valley', 'code' => 'NVL'],
    26	            ['name_ar' => 'السويس', 'name_en' => 'Suez', 'code' => 'SUZ'],
    27	            ['name_ar' => 'أسوان', 'name_en' => 'Aswan', 'code' => 'ASW'],
    28	            ['name_ar' => 'أسيوط', 'name_en' => 'Asyut', 'code' => 'ASY'],
    29	            ['name_ar' => 'بني سويف', 'name_en' => 'Beni Suef', 'code' => 'BNS'],
    30	            ['name_ar' => 'بورسعيد', 'name_en' => 'Port Said', 'code' => 'PTS'],
    31	            ['name_ar' => 'دمياط', 'name_en' => 'Damietta', 'code' => 'DMT'],
    32	            ['name_ar' => 'الشرقية', 'name_en' => 'Sharqia', 'code' => 'SHR'],
    33	            ['name_ar' => 'جنوب سيناء', 'name_en' => 'South Sinai', 'code' => 'SSN'],
    34	            ['name_ar' => 'كفر الشيخ', 'name_en' => 'Kafr El Sheikh', 'code' => 'KFS'],
    35	            ['name_ar' => 'مطروح', 'name_en' => 'Matrouh', 'code' => 'MTR'],
    36	            ['name_ar' => 'الأقصر', 'name_en' => 'Luxor', 'code' => 'LXR'],
    37	            ['name_ar' => 'قنا', 'name_en' => 'Qena', 'code' => 'QNA'],
    38	            ['name_ar' => 'شمال سيناء', 'name_en' => 'North Sinai', 'code' => 'NSN'],
    39	            ['name_ar' => 'سوهاج', 'name_en' => 'Sohag', 'code' => 'SHG'],
    40	        ];
    41	
    42	        foreach ($governorates as $index => $gov) {
    43	            Governorate::updateOrCreate(
    44	                ['code' => $gov['code']],
    45	                array_merge($gov, ['sort_order' => $index + 1]),
    46	            );
    47	        }
    48	    }
    49	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [440]: database/seeders/GovernorateSeeder.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [441/494]: database/seeders/MaritalStatusSeeder.php
│ LANGUAGE: php | LINES: 25 | SIZE: 736 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace Database\Seeders;
     4	
     5	use App\Models\MaritalStatus;
     6	use Illuminate\Database\Seeder;
     7	
     8	class MaritalStatusSeeder extends Seeder
     9	{
    10	    public function run(): void
    11	    {
    12	        $statuses = [
    13	            ['name_ar' => 'أعزب', 'name_en' => 'Single', 'sort_order' => 1],
    14	            ['name_ar' => 'متزوج', 'name_en' => 'Married', 'sort_order' => 2],
    15	            ['name_ar' => 'مطلق', 'name_en' => 'Divorced', 'sort_order' => 3],
    16	            ['name_ar' => 'أرمل', 'name_en' => 'Widowed', 'sort_order' => 4],
    17	        ];
    18	
    19	        foreach ($statuses as $status) {
    20	            MaritalStatus::updateOrCreate(
    21	                ['name_ar' => $status['name_ar']],
    22	                $status,
    23	            );
    24	        }
    25	    }
    26	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [441]: database/seeders/MaritalStatusSeeder.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [442/494]: database/seeders/MembershipStatusSeeder.php
│ LANGUAGE: php | LINES: 165 | SIZE: 6584 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace Database\Seeders;
     4	
     5	use App\Models\MembershipStatus;
     6	use Illuminate\Database\Seeder;
     7	
     8	class MembershipStatusSeeder extends Seeder
     9	{
    10	    public function run(): void
    11	    {
    12	        $statuses = [
    13	            [
    14	                'name_ar' => 'طلب جديد',
    15	                'name_en' => 'New Application',
    16	                'code' => 'NEW_APPLICATION',
    17	                'color' => 'info',
    18	                'icon' => 'heroicon-o-document-plus',
    19	                'description_ar' => 'تم تقديم طلب العضوية ولم يتم مراجعته بعد',
    20	                'allows_entry' => false,
    21	                'allows_renewal' => false,
    22	                'is_terminal' => false,
    23	                'sort_order' => 1,
    24	            ],
    25	            [
    26	                'name_ar' => 'قيد المراجعة',
    27	                'name_en' => 'Under Review',
    28	                'code' => 'UNDER_REVIEW',
    29	                'color' => 'warning',
    30	                'icon' => 'heroicon-o-clock',
    31	                'description_ar' => 'الطلب قيد المراجعة من قبل الإدارة',
    32	                'allows_entry' => false,
    33	                'allows_renewal' => false,
    34	                'is_terminal' => false,
    35	                'sort_order' => 2,
    36	            ],
    37	            [
    38	                'name_ar' => 'في انتظار الموافقة',
    39	                'name_en' => 'Pending Approval',
    40	                'code' => 'PENDING_APPROVAL',
    41	                'color' => 'warning',
    42	                'icon' => 'heroicon-o-hand-raised',
    43	                'description_ar' => 'في انتظار موافقة مجلس الإدارة',
    44	                'allows_entry' => false,
    45	                'allows_renewal' => false,
    46	                'is_terminal' => false,
    47	                'sort_order' => 3,
    48	            ],
    49	            [
    50	                'name_ar' => 'معتمد — في انتظار السداد',
    51	                'name_en' => 'Approved — Pending Payment',
    52	                'code' => 'APPROVED_PENDING_PAYMENT',
    53	                'color' => 'info',
    54	                'icon' => 'heroicon-o-banknotes',
    55	                'description_ar' => 'تمت الموافقة وفي انتظار سداد الرسوم',
    56	                'allows_entry' => false,
    57	                'allows_renewal' => false,
    58	                'is_terminal' => false,
    59	                'sort_order' => 4,
    60	            ],
    61	            [
    62	                'name_ar' => 'فعّال',
    63	                'name_en' => 'Active',
    64	                'code' => 'ACTIVE',
    65	                'color' => 'success',
    66	                'icon' => 'heroicon-o-check-circle',
    67	                'description_ar' => 'عضوية فعالة وسارية',
    68	                'allows_entry' => true,
    69	                'allows_renewal' => true,
    70	                'is_terminal' => false,
    71	                'sort_order' => 5,
    72	            ],
    73	            [
    74	                'name_ar' => 'موقوف',
    75	                'name_en' => 'Suspended',
    76	                'code' => 'SUSPENDED',
    77	                'color' => 'danger',
    78	                'icon' => 'heroicon-o-no-symbol',
    79	                'description_ar' => 'عضوية موقوفة بسبب مخالفة أو عدم السداد',
    80	                'allows_entry' => false,
    81	                'allows_renewal' => false,
    82	                'is_terminal' => false,
    83	                'sort_order' => 6,
    84	            ],
    85	            [
    86	                'name_ar' => 'مجمد',
    87	                'name_en' => 'Frozen',
    88	                'code' => 'FROZEN',
    89	                'color' => 'gray',
    90	                'icon' => 'heroicon-o-pause-circle',
    91	                'description_ar' => 'عضوية مجمدة مؤقتاً',
    92	                'allows_entry' => false,
    93	                'allows_renewal' => true,
    94	                'is_terminal' => false,
    95	                'sort_order' => 7,
    96	            ],
    97	            [
    98	                'name_ar' => 'منتهية — عدم سداد',
    99	                'name_en' => 'Expired — Non-Payment',
   100	                'code' => 'EXPIRED_NON_PAYMENT',
   101	                'color' => 'danger',
   102	                'icon' => 'heroicon-o-exclamation-triangle',
   103	                'description_ar' => 'انتهت العضوية بسبب عدم سداد الاشتراكات',
   104	                'allows_entry' => false,
   105	                'allows_renewal' => true,
   106	                'is_terminal' => false,
   107	                'sort_order' => 8,
   108	            ],
   109	            [
   110	                'name_ar' => 'ملغاة',
   111	                'name_en' => 'Revoked',
   112	                'code' => 'REVOKED',
   113	                'color' => 'danger',
   114	                'icon' => 'heroicon-o-x-circle',
   115	                'description_ar' => 'تم إلغاء العضوية نهائياً بقرار من مجلس الإدارة',
   116	                'allows_entry' => false,
   117	                'allows_renewal' => false,
   118	                'is_terminal' => true,
   119	                'sort_order' => 9,
   120	            ],
   121	            [
   122	                'name_ar' => 'مرفوض',
   123	                'name_en' => 'Rejected',
   124	                'code' => 'REJECTED',
   125	                'color' => 'danger',
   126	                'icon' => 'heroicon-o-x-mark',
   127	                'description_ar' => 'تم رفض طلب العضوية',
   128	                'allows_entry' => false,
   129	                'allows_renewal' => false,
   130	                'is_terminal' => true,
   131	                'sort_order' => 10,
   132	            ],
   133	            [
   134	                'name_ar' => 'منقولة',
   135	                'name_en' => 'Transferred',
   136	                'code' => 'TRANSFERRED',
   137	                'color' => 'gray',
   138	                'icon' => 'heroicon-o-arrow-right-circle',
   139	                'description_ar' => 'تم نقل العضوية لوريث أو مستفيد آخر',
   140	                'allows_entry' => false,
   141	                'allows_renewal' => false,
   142	                'is_terminal' => true,
   143	                'sort_order' => 11,
   144	            ],
   145	            [
   146	                'name_ar' => 'متوفى',
   147	                'name_en' => 'Deceased',
   148	                'code' => 'DECEASED',
   149	                'color' => 'gray',
   150	                'icon' => 'heroicon-o-heart',
   151	                'description_ar' => 'العضو متوفى — العضوية في انتظار إجراءات النقل',
   152	                'allows_entry' => false,
   153	                'allows_renewal' => false,
   154	                'is_terminal' => true,
   155	                'sort_order' => 12,
   156	            ],
   157	        ];
   158	
   159	        foreach ($statuses as $status) {
   160	            MembershipStatus::updateOrCreate(
   161	                ['code' => $status['code']],
   162	                $status,
   163	            );
   164	        }
   165	    }
   166	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [442]: database/seeders/MembershipStatusSeeder.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [443/494]: database/seeders/MembershipTypeSeeder.php
│ LANGUAGE: php | LINES: 112 | SIZE: 4472 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace Database\Seeders;
     4	
     5	use App\Models\MembershipType;
     6	use Illuminate\Database\Seeder;
     7	
     8	class MembershipTypeSeeder extends Seeder
     9	{
    10	    public function run(): void
    11	    {
    12	        $types = [
    13	            [
    14	                'name_ar' => 'عضوية عاملة',
    15	                'name_en' => 'Working Membership',
    16	                'code' => 'WORKING',
    17	                'description_ar' => 'عضوية كاملة الحقوق بما فيها التصويت والترشح',
    18	                'application_fee' => 5000.00,
    19	                'membership_value' => 50000.00,
    20	                'annual_subscription' => 5000.00,
    21	                'allows_voting' => true,
    22	                'allows_election' => true,
    23	                'is_transferable' => true,
    24	                'sort_order' => 1,
    25	            ],
    26	            [
    27	                'name_ar' => 'عضوية مؤقتة',
    28	                'name_en' => 'Temporary Membership',
    29	                'code' => 'TEMPORARY',
    30	                'description_ar' => 'عضوية محددة المدة بشروط خاصة',
    31	                'application_fee' => 2000.00,
    32	                'membership_value' => 20000.00,
    33	                'annual_subscription' => 3000.00,
    34	                'allows_voting' => false,
    35	                'allows_election' => false,
    36	                'is_transferable' => false,
    37	                'sort_order' => 2,
    38	            ],
    39	            [
    40	                'name_ar' => 'عضوية موسمية',
    41	                'name_en' => 'Seasonal Membership',
    42	                'code' => 'SEASONAL',
    43	                'description_ar' => 'عضوية لموسم الصيف أو الشتاء',
    44	                'application_fee' => 1000.00,
    45	                'membership_value' => 10000.00,
    46	                'annual_subscription' => 2000.00,
    47	                'allows_voting' => false,
    48	                'allows_election' => false,
    49	                'is_transferable' => false,
    50	                'sort_order' => 3,
    51	            ],
    52	            [
    53	                'name_ar' => 'عضوية انتساب',
    54	                'name_en' => 'Affiliate Membership',
    55	                'code' => 'AFFILIATE',
    56	                'description_ar' => 'عضوية بصلاحيات محدودة',
    57	                'application_fee' => 1500.00,
    58	                'membership_value' => 15000.00,
    59	                'annual_subscription' => 2500.00,
    60	                'allows_voting' => false,
    61	                'allows_election' => false,
    62	                'is_transferable' => false,
    63	                'sort_order' => 4,
    64	            ],
    65	            [
    66	                'name_ar' => 'عضوية رياضية محولة',
    67	                'name_en' => 'Athletic Conversion',
    68	                'code' => 'ATHLETIC',
    69	                'description_ar' => 'تحويل عضوية رياضي بعد 8 سنوات أو أكثر',
    70	                'application_fee' => 3000.00,
    71	                'membership_value' => 30000.00,
    72	                'annual_subscription' => 4000.00,
    73	                'allows_voting' => true,
    74	                'allows_election' => false,
    75	                'is_transferable' => true,
    76	                'sort_order' => 5,
    77	            ],
    78	            [
    79	                'name_ar' => 'عضوية مكتسبة',
    80	                'name_en' => 'Acquired Membership',
    81	                'code' => 'ACQUIRED',
    82	                'description_ar' => 'عضوية مكتسبة عن طريق الميراث أو الطلاق',
    83	                'application_fee' => 2000.00,
    84	                'membership_value' => 25000.00,
    85	                'annual_subscription' => 4000.00,
    86	                'allows_voting' => false,
    87	                'allows_election' => false,
    88	                'is_transferable' => false,
    89	                'sort_order' => 6,
    90	            ],
    91	            [
    92	                'name_ar' => 'عضوية فخرية',
    93	                'name_en' => 'Honorary Membership',
    94	                'code' => 'HONORARY',
    95	                'description_ar' => 'عضوية ممنوحة بقرار من مجلس الإدارة',
    96	                'application_fee' => 0.00,
    97	                'membership_value' => 0.00,
    98	                'annual_subscription' => 0.00,
    99	                'allows_voting' => false,
   100	                'allows_election' => false,
   101	                'is_transferable' => false,
   102	                'sort_order' => 7,
   103	            ],
   104	        ];
   105	
   106	        foreach ($types as $type) {
   107	            MembershipType::updateOrCreate(
   108	                ['code' => $type['code']],
   109	                $type,
   110	            );
   111	        }
   112	    }
   113	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [443]: database/seeders/MembershipTypeSeeder.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [444/494]: database/seeders/NationalitySeeder.php
│ LANGUAGE: php | LINES: 44 | SIZE: 3025 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace Database\Seeders;
     4	
     5	use App\Models\Nationality;
     6	use Illuminate\Database\Seeder;
     7	
     8	class NationalitySeeder extends Seeder
     9	{
    10	    public function run(): void
    11	    {
    12	        $nationalities = [
    13	            ['name_ar' => 'مصري', 'name_en' => 'Egyptian', 'code' => 'EG', 'demonym_ar' => 'مصري'],
    14	            ['name_ar' => 'سعودي', 'name_en' => 'Saudi', 'code' => 'SA', 'demonym_ar' => 'سعودي'],
    15	            ['name_ar' => 'إماراتي', 'name_en' => 'Emirati', 'code' => 'AE', 'demonym_ar' => 'إماراتي'],
    16	            ['name_ar' => 'كويتي', 'name_en' => 'Kuwaiti', 'code' => 'KW', 'demonym_ar' => 'كويتي'],
    17	            ['name_ar' => 'بحريني', 'name_en' => 'Bahraini', 'code' => 'BH', 'demonym_ar' => 'بحريني'],
    18	            ['name_ar' => 'عماني', 'name_en' => 'Omani', 'code' => 'OM', 'demonym_ar' => 'عماني'],
    19	            ['name_ar' => 'قطري', 'name_en' => 'Qatari', 'code' => 'QA', 'demonym_ar' => 'قطري'],
    20	            ['name_ar' => 'عراقي', 'name_en' => 'Iraqi', 'code' => 'IQ', 'demonym_ar' => 'عراقي'],
    21	            ['name_ar' => 'أردني', 'name_en' => 'Jordanian', 'code' => 'JO', 'demonym_ar' => 'أردني'],
    22	            ['name_ar' => 'سوري', 'name_en' => 'Syrian', 'code' => 'SY', 'demonym_ar' => 'سوري'],
    23	            ['name_ar' => 'لبناني', 'name_en' => 'Lebanese', 'code' => 'LB', 'demonym_ar' => 'لبناني'],
    24	            ['name_ar' => 'فلسطيني', 'name_en' => 'Palestinian', 'code' => 'PS', 'demonym_ar' => 'فلسطيني'],
    25	            ['name_ar' => 'ليبي', 'name_en' => 'Libyan', 'code' => 'LY', 'demonym_ar' => 'ليبي'],
    26	            ['name_ar' => 'تونسي', 'name_en' => 'Tunisian', 'code' => 'TN', 'demonym_ar' => 'تونسي'],
    27	            ['name_ar' => 'جزائري', 'name_en' => 'Algerian', 'code' => 'DZ', 'demonym_ar' => 'جزائري'],
    28	            ['name_ar' => 'مغربي', 'name_en' => 'Moroccan', 'code' => 'MA', 'demonym_ar' => 'مغربي'],
    29	            ['name_ar' => 'سوداني', 'name_en' => 'Sudanese', 'code' => 'SD', 'demonym_ar' => 'سوداني'],
    30	            ['name_ar' => 'يمني', 'name_en' => 'Yemeni', 'code' => 'YE', 'demonym_ar' => 'يمني'],
    31	            ['name_ar' => 'أمريكي', 'name_en' => 'American', 'code' => 'US', 'demonym_ar' => 'أمريكي'],
    32	            ['name_ar' => 'بريطاني', 'name_en' => 'British', 'code' => 'GB', 'demonym_ar' => 'بريطاني'],
    33	            ['name_ar' => 'فرنسي', 'name_en' => 'French', 'code' => 'FR', 'demonym_ar' => 'فرنسي'],
    34	            ['name_ar' => 'ألماني', 'name_en' => 'German', 'code' => 'DE', 'demonym_ar' => 'ألماني'],
    35	            ['name_ar' => 'أخرى', 'name_en' => 'Other', 'code' => 'OT', 'demonym_ar' => 'أخرى'],
    36	        ];
    37	
    38	        foreach ($nationalities as $index => $nat) {
    39	            Nationality::updateOrCreate(
    40	                ['code' => $nat['code']],
    41	                array_merge($nat, ['sort_order' => $index + 1]),
    42	            );
    43	        }
    44	    }
    45	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [444]: database/seeders/NationalitySeeder.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [445/494]: database/seeders/PaymentMethodSeeder.php
│ LANGUAGE: php | LINES: 27 | SIZE: 1070 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace Database\Seeders;
     4	
     5	use App\Models\PaymentMethod;
     6	use Illuminate\Database\Seeder;
     7	
     8	class PaymentMethodSeeder extends Seeder
     9	{
    10	    public function run(): void
    11	    {
    12	        $methods = [
    13	            ['name_ar' => 'نقدي', 'name_en' => 'Cash', 'code' => 'CASH', 'sort_order' => 1],
    14	            ['name_ar' => 'شيك', 'name_en' => 'Cheque', 'code' => 'CHEQUE', 'sort_order' => 2],
    15	            ['name_ar' => 'تحويل بنكي', 'name_en' => 'Bank Transfer', 'code' => 'BANK_TRANSFER', 'sort_order' => 3],
    16	            ['name_ar' => 'بطاقة ائتمان', 'name_en' => 'Credit Card', 'code' => 'CREDIT_CARD', 'sort_order' => 4],
    17	            ['name_ar' => 'فوري', 'name_en' => 'Fawry', 'code' => 'FAWRY', 'sort_order' => 5],
    18	            ['name_ar' => 'محفظة إلكترونية', 'name_en' => 'E-Wallet', 'code' => 'EWALLET', 'sort_order' => 6],
    19	        ];
    20	
    21	        foreach ($methods as $method) {
    22	            PaymentMethod::updateOrCreate(
    23	                ['code' => $method['code']],
    24	                $method,
    25	            );
    26	        }
    27	    }
    28	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [445]: database/seeders/PaymentMethodSeeder.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [446/494]: database/seeders/PenaltyTypeSeeder.php
│ LANGUAGE: php | LINES: 119 | SIZE: 5615 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	declare(strict_types=1);
     4	
     5	namespace Database\Seeders;
     6	
     7	use App\Models\PenaltyType;
     8	use Illuminate\Database\Seeder;
     9	
    10	class PenaltyTypeSeeder extends Seeder
    11	{
    12	    public function run(): void
    13	    {
    14	        $types = [
    15	            [
    16	                'code'                     => 'VERBAL_WARNING',
    17	                'name_ar'                  => 'إنذار شفهي',
    18	                'name_en'                  => 'Verbal Warning',
    19	                'description'              => 'إنذار شفهي يُسجل في ملف العضو',
    20	                'default_fine_amount'      => 0,
    21	                'requires_suspension'      => false,
    22	                'default_suspension_days'  => 0,
    23	                'requires_board_approval'  => false,
    24	                'display_order'            => 1,
    25	            ],
    26	            [
    27	                'code'                     => 'WRITTEN_WARNING',
    28	                'name_ar'                  => 'إنذار كتابي',
    29	                'name_en'                  => 'Written Warning',
    30	                'description'              => 'إنذار كتابي رسمي',
    31	                'default_fine_amount'      => 0,
    32	                'requires_suspension'      => false,
    33	                'default_suspension_days'  => 0,
    34	                'requires_board_approval'  => false,
    35	                'display_order'            => 2,
    36	            ],
    37	            [
    38	                'code'                     => 'FINE_SMALL',
    39	                'name_ar'                  => 'غرامة مالية بسيطة',
    40	                'name_en'                  => 'Small Fine',
    41	                'description'              => 'غرامة مالية بسيطة',
    42	                'default_fine_amount'      => 500.00,
    43	                'requires_suspension'      => false,
    44	                'default_suspension_days'  => 0,
    45	                'requires_board_approval'  => false,
    46	                'display_order'            => 3,
    47	            ],
    48	            [
    49	                'code'                     => 'FINE_LARGE',
    50	                'name_ar'                  => 'غرامة مالية كبيرة',
    51	                'name_en'                  => 'Large Fine',
    52	                'description'              => 'غرامة مالية كبيرة',
    53	                'default_fine_amount'      => 5000.00,
    54	                'requires_suspension'      => false,
    55	                'default_suspension_days'  => 0,
    56	                'requires_board_approval'  => true,
    57	                'display_order'            => 4,
    58	            ],
    59	            [
    60	                'code'                     => 'TEMP_SUSPENSION',
    61	                'name_ar'                  => 'إيقاف مؤقت',
    62	                'name_en'                  => 'Temporary Suspension',
    63	                'description'              => 'إيقاف مؤقت عن ممارسة الأنشطة',
    64	                'default_fine_amount'      => 0,
    65	                'requires_suspension'      => true,
    66	                'default_suspension_days'  => 30,
    67	                'requires_board_approval'  => true,
    68	                'display_order'            => 5,
    69	            ],
    70	            [
    71	                'code'                     => 'LONG_SUSPENSION',
    72	                'name_ar'                  => 'إيقاف طويل',
    73	                'name_en'                  => 'Long Suspension',
    74	                'description'              => 'إيقاف طويل المدة',
    75	                'default_fine_amount'      => 0,
    76	                'requires_suspension'      => true,
    77	                'default_suspension_days'  => 90,
    78	                'requires_board_approval'  => true,
    79	                'display_order'            => 6,
    80	            ],
    81	            [
    82	                'code'                     => 'FINE_WITH_SUSPENSION',
    83	                'name_ar'                  => 'غرامة مع إيقاف',
    84	                'name_en'                  => 'Fine with Suspension',
    85	                'description'              => 'غرامة مالية مصحوبة بإيقاف مؤقت',
    86	                'default_fine_amount'      => 2000.00,
    87	                'requires_suspension'      => true,
    88	                'default_suspension_days'  => 14,
    89	                'requires_board_approval'  => true,
    90	                'display_order'            => 7,
    91	            ],
    92	            [
    93	                'code'                     => 'FACILITY_BAN',
    94	                'name_ar'                  => 'حظر استخدام مرافق',
    95	                'name_en'                  => 'Facility Ban',
    96	                'description'              => 'حظر استخدام مرافق معينة بالنادي',
    97	                'default_fine_amount'      => 0,
    98	                'requires_suspension'      => false,
    99	                'default_suspension_days'  => 0,
   100	                'requires_board_approval'  => false,
   101	                'display_order'            => 8,
   102	            ],
   103	            [
   104	                'code'                     => 'MEMBERSHIP_REVOCATION',
   105	                'name_ar'                  => 'إلغاء العضوية',
   106	                'name_en'                  => 'Membership Revocation',
   107	                'description'              => 'إلغاء العضوية نهائياً',
   108	                'default_fine_amount'      => 0,
   109	                'requires_suspension'      => true,
   110	                'default_suspension_days'  => 3650, // ~10 years = permanent
   111	                'requires_board_approval'  => true,
   112	                'display_order'            => 9,
   113	            ],
   114	        ];
   115	
   116	        foreach ($types as $type) {
   117	            PenaltyType::updateOrCreate(['code' => $type['code']], $type);
   118	        }
   119	    }
   120	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [446]: database/seeders/PenaltyTypeSeeder.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [447/494]: database/seeders/ReligionSeeder.php
│ LANGUAGE: php | LINES: 25 | SIZE: 729 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace Database\Seeders;
     4	
     5	use App\Models\Religion;
     6	use Illuminate\Database\Seeder;
     7	
     8	class ReligionSeeder extends Seeder
     9	{
    10	    public function run(): void
    11	    {
    12	        $religions = [
    13	            ['name_ar' => 'مسلم', 'name_en' => 'Muslim', 'sort_order' => 1],
    14	            ['name_ar' => 'مسيحي', 'name_en' => 'Christian', 'sort_order' => 2],
    15	            ['name_ar' => 'يهودي', 'name_en' => 'Jewish', 'sort_order' => 3],
    16	            ['name_ar' => 'أخرى', 'name_en' => 'Other', 'sort_order' => 4],
    17	        ];
    18	
    19	        foreach ($religions as $religion) {
    20	            Religion::updateOrCreate(
    21	                ['name_ar' => $religion['name_ar']],
    22	                $religion,
    23	            );
    24	        }
    25	    }
    26	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [447]: database/seeders/ReligionSeeder.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [448/494]: database/seeders/RolesAndPermissionsSeeder.php
│ LANGUAGE: php | LINES: 119 | SIZE: 6113 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace Database\Seeders;
     4	
     5	use Illuminate\Database\Seeder;
     6	use Spatie\Permission\Models\Permission;
     7	use Spatie\Permission\Models\Role;
     8	
     9	class RolesAndPermissionsSeeder extends Seeder
    10	{
    11	    public function run(): void
    12	    {
    13	        // Reset cached roles and permissions
    14	        app()[\Spatie\Permission\PermissionRegistrar::class]->forgetCachedPermissions();
    15	
    16	        $modules = [
    17	            'member' => ['view_any', 'view', 'create', 'update', 'delete', 'archive', 'restore', 'print', 'export', 'import'],
    18	            'dependent' => ['view_any', 'view', 'create', 'update', 'delete', 'archive', 'restore'],
    19	            'receipt' => ['view_any', 'view', 'create', 'void', 'print', 'export'],
    20	            'subscription' => ['view_any', 'view', 'create', 'update', 'renew', 'bulk_renew'],
    21	            'card' => ['view_any', 'view', 'create', 'print', 'cancel', 'reissue'],
    22	            'document' => ['view_any', 'view', 'create', 'update', 'delete', 'verify', 'download'],
    23	            'violation' => ['view_any', 'view', 'create', 'update', 'resolve'],
    24	            'penalty' => ['view_any', 'view', 'create', 'update', 'waive'],
    25	            'transfer' => ['view_any', 'view', 'create', 'approve', 'reject'],
    26	            'installment_plan' => ['view_any', 'view', 'create', 'update', 'cancel'],
    27	            'board_offer' => ['view_any', 'view', 'create', 'update', 'activate', 'deactivate'],
    28	            'cash_register' => ['view_any', 'view', 'open', 'close', 'reconcile'],
    29	            'report' => ['view_any', 'view_financial', 'view_membership', 'view_subscription', 'view_violation', 'export'],
    30	            'setting' => ['view_any', 'update', 'manage_fees', 'manage_workflow', 'manage_roles'],
    31	            'audit' => ['view_any', 'view', 'export'],
    32	            'user' => ['view_any', 'view', 'create', 'update', 'delete', 'assign_roles'],
    33	            'facility' => ['view_any', 'view', 'create', 'update', 'delete'],
    34	            'booking' => ['view_any', 'view', 'create', 'update', 'cancel', 'check_in'],
    35	            'interview' => ['view_any', 'view', 'create', 'update', 'complete'],
    36	            'workflow' => ['view_any', 'view', 'advance', 'reject', 'override'],
    37	            'notification' => ['view_any', 'send', 'manage_templates'],
    38	        ];
    39	
    40	        $allPermissions = [];
    41	        foreach ($modules as $module => $actions) {
    42	            foreach ($actions as $action) {
    43	                $permissionName = "{$action}_{$module}";
    44	                $allPermissions[] = $permissionName;
    45	                Permission::firstOrCreate(['name' => $permissionName, 'guard_name' => 'web']);
    46	            }
    47	        }
    48	
    49	        // — Super Admin —
    50	        $superAdmin = Role::firstOrCreate(['name' => 'super_admin', 'guard_name' => 'web']);
    51	        $superAdmin->syncPermissions($allPermissions);
    52	
    53	        // — Board of Directors (read-only + reports) —
    54	        $board = Role::firstOrCreate(['name' => 'board_of_directors', 'guard_name' => 'web']);
    55	        $board->syncPermissions(
    56	            collect($allPermissions)->filter(fn ($p) => str_starts_with($p, 'view'))->values()->all()
    57	        );
    58	
    59	        // — Treasury / Finance Officer —
    60	        $treasury = Role::firstOrCreate(['name' => 'treasury', 'guard_name' => 'web']);
    61	        $treasury->syncPermissions([
    62	            ...$this->modulePermissions($modules, 'receipt'),
    63	            ...$this->modulePermissions($modules, 'cash_register'),
    64	            ...$this->modulePermissions($modules, 'installment_plan'),
    65	            ...$this->modulePermissions($modules, 'board_offer'),
    66	            ...$this->modulePermissions($modules, 'subscription'),
    67	            'view_any_member', 'view_member',
    68	            'view_any_report', 'view_financial_report', 'export_report',
    69	        ]);
    70	
    71	        // — Membership Officer —
    72	        $membership = Role::firstOrCreate(['name' => 'membership_officer', 'guard_name' => 'web']);
    73	        $membership->syncPermissions([
    74	            ...$this->modulePermissions($modules, 'member'),
    75	            ...$this->modulePermissions($modules, 'dependent'),
    76	            ...$this->modulePermissions($modules, 'document'),
    77	            ...$this->modulePermissions($modules, 'card'),
    78	            ...$this->modulePermissions($modules, 'subscription'),
    79	            ...$this->modulePermissions($modules, 'interview'),
    80	            ...$this->modulePermissions($modules, 'workflow'),
    81	            'view_any_receipt', 'view_receipt', 'create_receipt', 'print_receipt',
    82	            'view_any_report', 'view_membership_report',
    83	        ]);
    84	
    85	        // — Front Desk / Reception —
    86	        $frontDesk = Role::firstOrCreate(['name' => 'front_desk', 'guard_name' => 'web']);
    87	        $frontDesk->syncPermissions([
    88	            'view_any_member', 'view_member',
    89	            'view_any_dependent', 'view_dependent',
    90	            'view_any_card', 'view_card', 'print_card',
    91	            'view_any_receipt', 'view_receipt', 'create_receipt', 'print_receipt',
    92	            'view_any_booking', 'view_booking', 'create_booking', 'check_in_booking',
    93	            'view_any_subscription', 'view_subscription', 'renew_subscription',
    94	        ]);
    95	
    96	        // — Archive Officer —
    97	        $archive = Role::firstOrCreate(['name' => 'archive_officer', 'guard_name' => 'web']);
    98	        $archive->syncPermissions([
    99	            ...$this->modulePermissions($modules, 'document'),
   100	            'view_any_member', 'view_member',
   101	            'view_any_dependent', 'view_dependent',
   102	            'view_any_audit', 'view_audit', 'export_audit',
   103	        ]);
   104	
   105	        // — Auditor (read-only + export) —
   106	        $auditor = Role::firstOrCreate(['name' => 'auditor', 'guard_name' => 'web']);
   107	        $auditor->syncPermissions([
   108	            ...collect($allPermissions)->filter(fn ($p) => str_starts_with($p, 'view'))->values()->all(),
   109	            ...collect($allPermissions)->filter(fn ($p) => str_starts_with($p, 'export'))->values()->all(),
   110	        ]);
   111	    }
   112	
   113	    private function modulePermissions(array $modules, string $module): array
   114	    {
   115	        return array_map(
   116	            fn ($action) => "{$action}_{$module}",
   117	            $modules[$module] ?? [],
   118	        );
   119	    }
   120	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [448]: database/seeders/RolesAndPermissionsSeeder.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [449/494]: database/seeders/SystemSettingsSeeder.php
│ LANGUAGE: php | LINES: 86 | SIZE: 16476 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace Database\Seeders;
     4	
     5	use App\Models\SystemSetting;
     6	use Illuminate\Database\Seeder;
     7	
     8	class SystemSettingsSeeder extends Seeder
     9	{
    10	    public function run(): void
    11	    {
    12	        $settings = [
    13	            // ─── Club Identity ───────────────────────────────────────
    14	            ['setting_key' => 'club.name_ar', 'setting_value' => 'نادي الآركيد', 'value_type' => 'string', 'group_key' => 'club', 'sub_group' => 'identity', 'description_ar' => 'اسم النادي بالعربية', 'description_en' => 'Club Name (Arabic)', 'display_order' => 1],
    15	            ['setting_key' => 'club.name_en', 'setting_value' => 'AL-ARCADE Club', 'value_type' => 'string', 'group_key' => 'club', 'sub_group' => 'identity', 'description_ar' => 'اسم النادي بالإنجليزية', 'description_en' => 'Club Name (English)', 'display_order' => 2],
    16	            ['setting_key' => 'club.phone', 'setting_value' => '', 'value_type' => 'string', 'group_key' => 'club', 'sub_group' => 'identity', 'description_ar' => 'رقم هاتف النادي', 'description_en' => 'Club Phone Number', 'display_order' => 3],
    17	            ['setting_key' => 'club.email', 'setting_value' => '', 'value_type' => 'string', 'group_key' => 'club', 'sub_group' => 'identity', 'description_ar' => 'البريد الإلكتروني للنادي', 'description_en' => 'Club Email', 'display_order' => 4],
    18	            ['setting_key' => 'club.address_ar', 'setting_value' => '', 'value_type' => 'string', 'group_key' => 'club', 'sub_group' => 'identity', 'description_ar' => 'عنوان النادي بالعربية', 'description_en' => 'Club Address (Arabic)', 'display_order' => 5],
    19	            ['setting_key' => 'club.address_en', 'setting_value' => '', 'value_type' => 'string', 'group_key' => 'club', 'sub_group' => 'identity', 'description_ar' => 'عنوان النادي بالإنجليزية', 'description_en' => 'Club Address (English)', 'display_order' => 6],
    20	            ['setting_key' => 'club.logo_path', 'setting_value' => '', 'value_type' => 'string', 'group_key' => 'club', 'sub_group' => 'identity', 'description_ar' => 'مسار شعار النادي', 'description_en' => 'Club Logo Path', 'display_order' => 7],
    21	            ['setting_key' => 'club.tax_number', 'setting_value' => '', 'value_type' => 'string', 'group_key' => 'club', 'sub_group' => 'identity', 'description_ar' => 'الرقم الضريبي', 'description_en' => 'Tax Number', 'display_order' => 8],
    22	            ['setting_key' => 'club.registration_number', 'setting_value' => '', 'value_type' => 'string', 'group_key' => 'club', 'sub_group' => 'identity', 'description_ar' => 'رقم تسجيل النادي', 'description_en' => 'Club Registration Number', 'display_order' => 9],
    23	
    24	            // ─── Membership Settings ─────────────────────────────────
    25	            ['setting_key' => 'membership.max_dependents', 'setting_value' => '10', 'value_type' => 'integer', 'group_key' => 'membership', 'sub_group' => 'limits', 'description_ar' => 'أقصى عدد تابعين لكل عضو', 'description_en' => 'Max Dependents Per Member', 'display_order' => 10],
    26	            ['setting_key' => 'membership.require_national_id', 'setting_value' => 'true', 'value_type' => 'boolean', 'group_key' => 'membership', 'sub_group' => 'requirements', 'description_ar' => 'إلزام الرقم القومي', 'description_en' => 'Require National ID', 'display_order' => 11],
    27	            ['setting_key' => 'membership.require_sponsor', 'setting_value' => 'true', 'value_type' => 'boolean', 'group_key' => 'membership', 'sub_group' => 'requirements', 'description_ar' => 'إلزام وجود مزكي', 'description_en' => 'Require Sponsor', 'display_order' => 12],
    28	            ['setting_key' => 'membership.min_sponsor_count', 'setting_value' => '2', 'value_type' => 'integer', 'group_key' => 'membership', 'sub_group' => 'requirements', 'description_ar' => 'الحد الأدنى لعدد المزكين', 'description_en' => 'Min Sponsor Count', 'display_order' => 13],
    29	            ['setting_key' => 'membership.require_interview', 'setting_value' => 'true', 'value_type' => 'boolean', 'group_key' => 'membership', 'sub_group' => 'requirements', 'description_ar' => 'إلزام المقابلة الشخصية', 'description_en' => 'Require Interview', 'display_order' => 14],
    30	            ['setting_key' => 'membership.require_board_approval', 'setting_value' => 'true', 'value_type' => 'boolean', 'group_key' => 'membership', 'sub_group' => 'requirements', 'description_ar' => 'إلزام موافقة مجلس الإدارة', 'description_en' => 'Require Board Approval', 'display_order' => 15],
    31	            ['setting_key' => 'membership.auto_number_on_activation', 'setting_value' => 'true', 'value_type' => 'boolean', 'group_key' => 'membership', 'sub_group' => 'numbering', 'description_ar' => 'ترقيم تلقائي عند التفعيل', 'description_en' => 'Auto Number on Activation', 'display_order' => 16],
    32	            ['setting_key' => 'membership.min_age', 'setting_value' => '18', 'value_type' => 'integer', 'group_key' => 'membership', 'sub_group' => 'limits', 'description_ar' => 'أقل سن للعضوية', 'description_en' => 'Minimum Membership Age', 'display_order' => 17],
    33	            ['setting_key' => 'membership.max_age', 'setting_value' => '0', 'value_type' => 'integer', 'group_key' => 'membership', 'sub_group' => 'limits', 'description_ar' => 'أقصى سن للعضوية (0 = بلا حد)', 'description_en' => 'Maximum Membership Age (0 = no limit)', 'display_order' => 18],
    34	
    35	            // ─── Dependent Settings ──────────────────────────────────
    36	            ['setting_key' => 'dependent.son_max_age', 'setting_value' => '21', 'value_type' => 'integer', 'group_key' => 'dependent', 'sub_group' => 'age_limits', 'description_ar' => 'أقصى سن للابن', 'description_en' => 'Son Max Age', 'display_order' => 20],
    37	            ['setting_key' => 'dependent.daughter_max_age', 'setting_value' => '0', 'value_type' => 'integer', 'group_key' => 'dependent', 'sub_group' => 'age_limits', 'description_ar' => 'أقصى سن للابنة (0 = بلا حد)', 'description_en' => 'Daughter Max Age (0 = no limit)', 'display_order' => 21],
    38	            ['setting_key' => 'dependent.age_check_warn_months', 'setting_value' => '6', 'value_type' => 'integer', 'group_key' => 'dependent', 'sub_group' => 'age_limits', 'description_ar' => 'التحذير قبل بلوغ السن بأشهر', 'description_en' => 'Age Warning Months Before', 'display_order' => 22],
    39	
    40	            // ─── Financial Settings ──────────────────────────────────
    41	            ['setting_key' => 'financial.currency', 'setting_value' => 'EGP', 'value_type' => 'string', 'group_key' => 'financial', 'sub_group' => 'general', 'description_ar' => 'العملة', 'description_en' => 'Currency', 'display_order' => 30],
    42	            ['setting_key' => 'financial.currency_name_ar', 'setting_value' => 'جنيه مصري', 'value_type' => 'string', 'group_key' => 'financial', 'sub_group' => 'general', 'description_ar' => 'اسم العملة بالعربية', 'description_en' => 'Currency Name (Arabic)', 'display_order' => 31],
    43	            ['setting_key' => 'financial.late_fee_enabled', 'setting_value' => 'true', 'value_type' => 'boolean', 'group_key' => 'financial', 'sub_group' => 'late_fees', 'description_ar' => 'تفعيل رسوم التأخير', 'description_en' => 'Late Fee Enabled', 'display_order' => 32],
    44	            ['setting_key' => 'financial.late_fee_percentage', 'setting_value' => '10', 'value_type' => 'decimal', 'group_key' => 'financial', 'sub_group' => 'late_fees', 'description_ar' => 'نسبة رسوم التأخير %', 'description_en' => 'Late Fee Percentage', 'display_order' => 33],
    45	            ['setting_key' => 'financial.late_fee_max_cap', 'setting_value' => '500', 'value_type' => 'decimal', 'group_key' => 'financial', 'sub_group' => 'late_fees', 'description_ar' => 'الحد الأقصى لرسوم التأخير', 'description_en' => 'Late Fee Max Cap', 'display_order' => 34],
    46	            ['setting_key' => 'financial.late_fee_grace_days', 'setting_value' => '30', 'value_type' => 'integer', 'group_key' => 'financial', 'sub_group' => 'late_fees', 'description_ar' => 'أيام السماح قبل احتساب التأخير', 'description_en' => 'Late Fee Grace Days', 'display_order' => 35],
    47	            ['setting_key' => 'financial.allow_partial_payment', 'setting_value' => 'false', 'value_type' => 'boolean', 'group_key' => 'financial', 'sub_group' => 'payments', 'description_ar' => 'السماح بالسداد الجزئي', 'description_en' => 'Allow Partial Payment', 'display_order' => 36],
    48	            ['setting_key' => 'financial.allow_installments', 'setting_value' => 'true', 'value_type' => 'boolean', 'group_key' => 'financial', 'sub_group' => 'payments', 'description_ar' => 'السماح بالتقسيط', 'description_en' => 'Allow Installments', 'display_order' => 37],
    49	            ['setting_key' => 'financial.max_installments', 'setting_value' => '12', 'value_type' => 'integer', 'group_key' => 'financial', 'sub_group' => 'payments', 'description_ar' => 'أقصى عدد أقساط', 'description_en' => 'Max Installments', 'display_order' => 38],
    50	            ['setting_key' => 'financial.subscription_due_month', 'setting_value' => '1', 'value_type' => 'integer', 'group_key' => 'financial', 'sub_group' => 'subscriptions', 'description_ar' => 'شهر استحقاق الاشتراك', 'description_en' => 'Subscription Due Month', 'display_order' => 39],
    51	            ['setting_key' => 'financial.subscription_due_day', 'setting_value' => '1', 'value_type' => 'integer', 'group_key' => 'financial', 'sub_group' => 'subscriptions', 'description_ar' => 'يوم استحقاق الاشتراك', 'description_en' => 'Subscription Due Day', 'display_order' => 40],
    52	
    53	            // ─── Card Settings ───────────────────────────────────────
    54	            ['setting_key' => 'card.validity_months', 'setting_value' => '12', 'value_type' => 'integer', 'group_key' => 'card', 'sub_group' => 'general', 'description_ar' => 'مدة صلاحية الكارنيه بالأشهر', 'description_en' => 'Card Validity Months', 'display_order' => 50],
    55	            ['setting_key' => 'card.auto_renew', 'setting_value' => 'false', 'value_type' => 'boolean', 'group_key' => 'card', 'sub_group' => 'general', 'description_ar' => 'تجديد تلقائي للكارنيه', 'description_en' => 'Auto Renew Card', 'display_order' => 51],
    56	            ['setting_key' => 'card.photo_required', 'setting_value' => 'true', 'value_type' => 'boolean', 'group_key' => 'card', 'sub_group' => 'general', 'description_ar' => 'إلزام الصورة للكارنيه', 'description_en' => 'Photo Required for Card', 'display_order' => 52],
    57	
    58	            // ─── Notification Settings ───────────────────────────────
    59	            ['setting_key' => 'notification.subscription_reminder_days', 'setting_value' => '30', 'value_type' => 'integer', 'group_key' => 'notification', 'sub_group' => 'reminders', 'description_ar' => 'تذكير بالاشتراك قبل أيام', 'description_en' => 'Subscription Reminder Days Before', 'display_order' => 60],
    60	            ['setting_key' => 'notification.card_expiry_reminder_days', 'setting_value' => '30', 'value_type' => 'integer', 'group_key' => 'notification', 'sub_group' => 'reminders', 'description_ar' => 'تذكير بانتهاء الكارنيه قبل أيام', 'description_en' => 'Card Expiry Reminder Days Before', 'display_order' => 61],
    61	            ['setting_key' => 'notification.installment_reminder_days', 'setting_value' => '7', 'value_type' => 'integer', 'group_key' => 'notification', 'sub_group' => 'reminders', 'description_ar' => 'تذكير بالقسط قبل أيام', 'description_en' => 'Installment Reminder Days Before', 'display_order' => 62],
    62	            ['setting_key' => 'notification.email_enabled', 'setting_value' => 'false', 'value_type' => 'boolean', 'group_key' => 'notification', 'sub_group' => 'channels', 'description_ar' => 'تفعيل إشعارات البريد', 'description_en' => 'Email Notifications Enabled', 'display_order' => 63],
    63	            ['setting_key' => 'notification.sms_enabled', 'setting_value' => 'false', 'value_type' => 'boolean', 'group_key' => 'notification', 'sub_group' => 'channels', 'description_ar' => 'تفعيل إشعارات الرسائل النصية', 'description_en' => 'SMS Notifications Enabled', 'display_order' => 64],
    64	
    65	            // ─── System Settings ─────────────────────────────────────
    66	            ['setting_key' => 'system.date_format', 'setting_value' => 'd/m/Y', 'value_type' => 'string', 'group_key' => 'system', 'sub_group' => 'display', 'description_ar' => 'تنسيق التاريخ', 'description_en' => 'Date Format', 'display_order' => 70],
    67	            ['setting_key' => 'system.timezone', 'setting_value' => 'Africa/Cairo', 'value_type' => 'string', 'group_key' => 'system', 'sub_group' => 'display', 'description_ar' => 'المنطقة الزمنية', 'description_en' => 'Timezone', 'display_order' => 71],
    68	            ['setting_key' => 'system.rows_per_page', 'setting_value' => '25', 'value_type' => 'integer', 'group_key' => 'system', 'sub_group' => 'display', 'description_ar' => 'عدد الصفوف في الصفحة', 'description_en' => 'Rows Per Page', 'display_order' => 72],
    69	            ['setting_key' => 'system.audit_retention_days', 'setting_value' => '1825', 'value_type' => 'integer', 'group_key' => 'system', 'sub_group' => 'maintenance', 'description_ar' => 'مدة الاحتفاظ بسجلات المراجعة بالأيام', 'description_en' => 'Audit Retention Days', 'display_order' => 73],
    70	            ['setting_key' => 'system.backup_enabled', 'setting_value' => 'true', 'value_type' => 'boolean', 'group_key' => 'system', 'sub_group' => 'maintenance', 'description_ar' => 'تفعيل النسخ الاحتياطي', 'description_en' => 'Backup Enabled', 'display_order' => 74],
    71	            ['setting_key' => 'system.maintenance_mode', 'setting_value' => 'false', 'value_type' => 'boolean', 'group_key' => 'system', 'sub_group' => 'maintenance', 'description_ar' => 'وضع الصيانة', 'description_en' => 'Maintenance Mode', 'display_order' => 75],
    72	
    73	            // ─── Receipt Settings ────────────────────────────────────
    74	            ['setting_key' => 'receipt.header_text_ar', 'setting_value' => 'إيصال سداد', 'value_type' => 'string', 'group_key' => 'receipt', 'sub_group' => 'printing', 'description_ar' => 'عنوان الإيصال بالعربية', 'description_en' => 'Receipt Header (Arabic)', 'display_order' => 80],
    75	            ['setting_key' => 'receipt.footer_text_ar', 'setting_value' => 'شكراً لسدادكم - هذا الإيصال سند رسمي', 'value_type' => 'string', 'group_key' => 'receipt', 'sub_group' => 'printing', 'description_ar' => 'تذييل الإيصال بالعربية', 'description_en' => 'Receipt Footer (Arabic)', 'display_order' => 81],
    76	            ['setting_key' => 'receipt.show_qr_code', 'setting_value' => 'true', 'value_type' => 'boolean', 'group_key' => 'receipt', 'sub_group' => 'printing', 'description_ar' => 'إظهار رمز QR في الإيصال', 'description_en' => 'Show QR Code on Receipt', 'display_order' => 82],
    77	            ['setting_key' => 'receipt.copies_count', 'setting_value' => '2', 'value_type' => 'integer', 'group_key' => 'receipt', 'sub_group' => 'printing', 'description_ar' => 'عدد نسخ الإيصال', 'description_en' => 'Receipt Copies Count', 'display_order' => 83],
    78	        ];
    79	
    80	        foreach ($settings as $setting) {
    81	            SystemSetting::updateOrCreate(
    82	                ['setting_key' => $setting['setting_key']],
    83	                $setting
    84	            );
    85	        }
    86	    }
    87	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [449]: database/seeders/SystemSettingsSeeder.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [450/494]: database/seeders/ViolationTypeSeeder.php
│ LANGUAGE: php | LINES: 120 | SIZE: 5610 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	declare(strict_types=1);
     4	
     5	namespace Database\Seeders;
     6	
     7	use App\Models\ViolationType;
     8	use Illuminate\Database\Seeder;
     9	
    10	class ViolationTypeSeeder extends Seeder
    11	{
    12	    public function run(): void
    13	    {
    14	        $types = [
    15	            [
    16	                'code'                   => 'MISCONDUCT',
    17	                'name_ar'                => 'سوء سلوك',
    18	                'name_en'                => 'Misconduct',
    19	                'description'            => 'سلوك غير لائق داخل النادي',
    20	                'default_severity'       => 'minor',
    21	                'requires_investigation' => false,
    22	                'requires_board_review'  => false,
    23	                'display_order'          => 1,
    24	            ],
    25	            [
    26	                'code'                   => 'PROPERTY_DAMAGE',
    27	                'name_ar'                => 'إتلاف ممتلكات',
    28	                'name_en'                => 'Property Damage',
    29	                'description'            => 'إتلاف أو تخريب ممتلكات النادي',
    30	                'default_severity'       => 'moderate',
    31	                'requires_investigation' => true,
    32	                'requires_board_review'  => false,
    33	                'display_order'          => 2,
    34	            ],
    35	            [
    36	                'code'                   => 'VERBAL_ABUSE',
    37	                'name_ar'                => 'إساءة لفظية',
    38	                'name_en'                => 'Verbal Abuse',
    39	                'description'            => 'إساءة لفظية تجاه عضو أو موظف',
    40	                'default_severity'       => 'moderate',
    41	                'requires_investigation' => true,
    42	                'requires_board_review'  => false,
    43	                'display_order'          => 3,
    44	            ],
    45	            [
    46	                'code'                   => 'PHYSICAL_ALTERCATION',
    47	                'name_ar'                => 'اعتداء جسدي',
    48	                'name_en'                => 'Physical Altercation',
    49	                'description'            => 'اشتباك بدني أو اعتداء جسدي',
    50	                'default_severity'       => 'major',
    51	                'requires_investigation' => true,
    52	                'requires_board_review'  => true,
    53	                'display_order'          => 4,
    54	            ],
    55	            [
    56	                'code'                   => 'RULE_VIOLATION',
    57	                'name_ar'                => 'مخالفة لوائح',
    58	                'name_en'                => 'Rule Violation',
    59	                'description'            => 'مخالفة لوائح وقوانين النادي',
    60	                'default_severity'       => 'minor',
    61	                'requires_investigation' => false,
    62	                'requires_board_review'  => false,
    63	                'display_order'          => 5,
    64	            ],
    65	            [
    66	                'code'                   => 'FRAUD',
    67	                'name_ar'                => 'احتيال',
    68	                'name_en'                => 'Fraud',
    69	                'description'            => 'تزوير مستندات أو احتيال مالي',
    70	                'default_severity'       => 'critical',
    71	                'requires_investigation' => true,
    72	                'requires_board_review'  => true,
    73	                'display_order'          => 6,
    74	            ],
    75	            [
    76	                'code'                   => 'UNAUTHORIZED_ACCESS',
    77	                'name_ar'                => 'دخول غير مصرح',
    78	                'name_en'                => 'Unauthorized Access',
    79	                'description'            => 'دخول مناطق غير مصرح بها',
    80	                'default_severity'       => 'moderate',
    81	                'requires_investigation' => true,
    82	                'requires_board_review'  => false,
    83	                'display_order'          => 7,
    84	            ],
    85	            [
    86	                'code'                   => 'GUEST_VIOLATION',
    87	                'name_ar'                => 'مخالفة ضيف',
    88	                'name_en'                => 'Guest Violation',
    89	                'description'            => 'مخالفة ارتكبها ضيف عضو',
    90	                'default_severity'       => 'minor',
    91	                'requires_investigation' => false,
    92	                'requires_board_review'  => false,
    93	                'display_order'          => 8,
    94	            ],
    95	            [
    96	                'code'                   => 'SAFETY_VIOLATION',
    97	                'name_ar'                => 'مخالفة أمان',
    98	                'name_en'                => 'Safety Violation',
    99	                'description'            => 'مخالفة إجراءات السلامة والأمان',
   100	                'default_severity'       => 'major',
   101	                'requires_investigation' => true,
   102	                'requires_board_review'  => true,
   103	                'display_order'          => 9,
   104	            ],
   105	            [
   106	                'code'                   => 'PAYMENT_DEFAULT',
   107	                'name_ar'                => 'تخلف عن السداد',
   108	                'name_en'                => 'Payment Default',
   109	                'description'            => 'التخلف المتكرر عن سداد المستحقات المالية',
   110	                'default_severity'       => 'moderate',
   111	                'requires_investigation' => false,
   112	                'requires_board_review'  => false,
   113	                'display_order'          => 10,
   114	            ],
   115	        ];
   116	
   117	        foreach ($types as $type) {
   118	            ViolationType::updateOrCreate(['code' => $type['code']], $type);
   119	        }
   120	    }
   121	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [450]: database/seeders/ViolationTypeSeeder.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [451/494]: postcss.config.js
│ LANGUAGE: javascript | LINES: 5 | SIZE: 92 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	export default {
     2	    plugins: {
     3	        tailwindcss: {},
     4	        autoprefixer: {},
     5	    },
     6	};│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [451]: postcss.config.js


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [452/494]: resources/css/app.css
│ LANGUAGE: css | LINES: 2 | SIZE: 58 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	@tailwind base;
     2	@tailwind components;
     3	@tailwind utilities;│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [452]: resources/css/app.css


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [453/494]: resources/css/filament/admin/theme.css
│ LANGUAGE: css | LINES: 34 | SIZE: 804 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	@import '/vendor/filament/filament/resources/css/theme.css';
     2	
     3	@import url('https://fonts.googleapis.com/css2?family=Cairo:wght@200;300;400;500;600;700;800;900&family=Tajawal:wght@200;300;400;500;700;800;900&display=swap');
     4	
     5	:root {
     6	    --font-family: 'Cairo', 'Tajawal', sans-serif;
     7	}
     8	
     9	[dir="rtl"] .fi-sidebar-nav {
    10	    font-family: 'Cairo', 'Tajawal', sans-serif;
    11	}
    12	
    13	.fi-body {
    14	    font-family: 'Cairo', 'Tajawal', sans-serif;
    15	}
    16	
    17	/* RTL tweaks for Filament */
    18	[dir="rtl"] .fi-ta-text {
    19	    text-align: right;
    20	}
    21	
    22	[dir="rtl"] .fi-fo-field-wrp {
    23	    text-align: right;
    24	}
    25	
    26	/* Print styles for cards and receipts */
    27	@media print {
    28	    .fi-sidebar, .fi-topbar, .fi-header, .fi-footer {
    29	        display: none !important;
    30	    }
    31	    .fi-main {
    32	        margin: 0 !important;
    33	        padding: 0 !important;
    34	    }
    35	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [453]: resources/css/filament/admin/theme.css


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [454/494]: resources/js/app.js
│ LANGUAGE: javascript | LINES: 1 | SIZE: 55 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	// AL-ARCADE Club Management ERP
     2	// Main JS entry point│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [454]: resources/js/app.js


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [455/494]: resources/views/filament/pages/backup-management.blade.php
│ LANGUAGE: php | LINES: 97 | SIZE: 5622 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<x-filament-panels::page>
     2	    <x-filament::section>
     3	        <x-slot name="heading">
     4	            <div class="flex items-center gap-2">
     5	                <x-heroicon-o-server-stack class="w-5 h-5" />
     6	                النسخ الاحتياطية المتاحة ({{ $backups->count() }})
     7	            </div>
     8	        </x-slot>
     9	
    10	        @if($backups->isEmpty())
    11	            <div class="text-center py-8 text-gray-500">
    12	                <x-heroicon-o-inbox class="w-12 h-12 mx-auto mb-3 text-gray-400" />
    13	                <p class="text-lg font-medium">لا توجد نسخ احتياطية</p>
    14	                <p class="text-sm">استخدم زر "إنشاء نسخة احتياطية" لإنشاء واحدة جديدة</p>
    15	            </div>
    16	        @else
    17	            <div class="overflow-x-auto">
    18	                <table class="w-full text-sm">
    19	                    <thead>
    20	                        <tr class="border-b border-gray-200 dark:border-gray-700">
    21	                            <th class="text-right py-3 px-4 font-medium text-gray-600 dark:text-gray-400">اسم الملف</th>
    22	                            <th class="text-right py-3 px-4 font-medium text-gray-600 dark:text-gray-400">الحجم</th>
    23	                            <th class="text-right py-3 px-4 font-medium text-gray-600 dark:text-gray-400">تاريخ الإنشاء</th>
    24	                            <th class="text-right py-3 px-4 font-medium text-gray-600 dark:text-gray-400">الإجراءات</th>
    25	                        </tr>
    26	                    </thead>
    27	                    <tbody>
    28	                        @foreach($backups as $backup)
    29	                            <tr class="border-b border-gray-100 dark:border-gray-800 hover:bg-gray-50 dark:hover:bg-gray-900">
    30	                                <td class="py-3 px-4">
    31	                                    <div class="flex items-center gap-2">
    32	                                        <x-heroicon-o-document class="w-5 h-5 text-gray-400" />
    33	                                        <span class="font-mono text-xs">{{ $backup['filename'] }}</span>
    34	                                    </div>
    35	                                </td>
    36	                                <td class="py-3 px-4">
    37	                                    <span class="inline-flex items-center px-2 py-1 rounded-full text-xs font-medium bg-blue-100 text-blue-700 dark:bg-blue-900 dark:text-blue-300">
    38	                                        {{ $backup['size_formatted'] }}
    39	                                    </span>
    40	                                </td>
    41	                                <td class="py-3 px-4 text-gray-600 dark:text-gray-400">
    42	                                    {{ $backup['last_modified']->format('d/m/Y H:i:s') }}
    43	                                </td>
    44	                                <td class="py-3 px-4">
    45	                                    <div class="flex items-center gap-2">
    46	                                        <x-filament::button
    47	                                            size="xs"
    48	                                            color="info"
    49	                                            icon="heroicon-o-arrow-down-tray"
    50	                                            wire:click="downloadBackup('{{ $backup['filename'] }}')"
    51	                                        >
    52	                                            تنزيل
    53	                                        </x-filament::button>
    54	
    55	                                        <x-filament::button
    56	                                            size="xs"
    57	                                            color="danger"
    58	                                            icon="heroicon-o-trash"
    59	                                            wire:click="deleteBackup('{{ $backup['filename'] }}')"
    60	                                            wire:confirm="هل أنت متأكد من حذف هذه النسخة الاحتياطية؟"
    61	                                        >
    62	                                            حذف
    63	                                        </x-filament::button>
    64	                                    </div>
    65	                                </td>
    66	                            </tr>
    67	                        @endforeach
    68	                    </tbody>
    69	                </table>
    70	            </div>
    71	        @endif
    72	    </x-filament::section>
    73	
    74	    <x-filament::section>
    75	        <x-slot name="heading">معلومات</x-slot>
    76	
    77	        <div class="grid grid-cols-1 md:grid-cols-3 gap-4">
    78	            <div class="p-4 bg-blue-50 dark:bg-blue-950 rounded-lg">
    79	                <div class="text-2xl font-bold text-blue-600 dark:text-blue-400">{{ $backups->count() }}</div>
    80	                <div class="text-sm text-blue-500 dark:text-blue-300">إجمالي النسخ</div>
    81	            </div>
    82	
    83	            <div class="p-4 bg-green-50 dark:bg-green-950 rounded-lg">
    84	                <div class="text-2xl font-bold text-green-600 dark:text-green-400">
    85	                    {{ $backups->isNotEmpty() ? $backups->first()['size_formatted'] : '0 B' }}
    86	                </div>
    87	                <div class="text-sm text-green-500 dark:text-green-300">حجم أحدث نسخة</div>
    88	            </div>
    89	
    90	            <div class="p-4 bg-purple-50 dark:bg-purple-950 rounded-lg">
    91	                <div class="text-2xl font-bold text-purple-600 dark:text-purple-400">
    92	                    {{ $backups->isNotEmpty() ? $backups->first()['last_modified']->diffForHumans() : '—' }}
    93	                </div>
    94	                <div class="text-sm text-purple-500 dark:text-purple-300">آخر نسخة احتياطية</div>
    95	            </div>
    96	        </div>
    97	    </x-filament::section>
    98	</x-filament-panels::page>│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [455]: resources/views/filament/pages/backup-management.blade.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [456/494]: resources/views/filament/pages/card-preview.blade.php
│ LANGUAGE: php | LINES: 180 | SIZE: 11419 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<x-filament-panels::page>
     2	    @if(!$cardData)
     3	        <div class="text-center py-12">
     4	            <x-heroicon-o-identification class="w-16 h-16 mx-auto text-gray-400 mb-4" />
     5	            <h3 class="text-lg font-medium text-gray-900 dark:text-gray-100 mb-2">لم يتم تحديد كارنيه</h3>
     6	            <p class="text-gray-500 dark:text-gray-400 mb-6">يرجى اختيار كارنيه من قائمة الكروت للمعاينة.</p>
     7	            <a href="{{ route('filament.admin.resources.member-cards.index') }}"
     8	               class="inline-flex items-center px-4 py-2 bg-primary-600 text-white rounded-lg hover:bg-primary-700 transition">
     9	                <x-heroicon-s-arrow-right class="w-4 h-4 ml-2" />
    10	                العودة لقائمة الكروت
    11	            </a>
    12	        </div>
    13	    @else
    14	        {{-- Eligibility Warnings --}}
    15	        @if(!$isEligible && count($eligibilityErrors) > 0)
    16	            <div class="bg-red-50 dark:bg-red-900/20 border border-red-200 dark:border-red-800 rounded-xl p-4 mb-6">
    17	                <div class="flex items-center gap-2 mb-2">
    18	                    <x-heroicon-s-exclamation-triangle class="w-5 h-5 text-red-600" />
    19	                    <h4 class="font-bold text-red-800 dark:text-red-200">تنبيهات الطباعة</h4>
    20	                </div>
    21	                <ul class="list-disc list-inside space-y-1 text-red-700 dark:text-red-300 text-sm">
    22	                    @foreach($eligibilityErrors as $error)
    23	                        <li>{{ $error }}</li>
    24	                    @endforeach
    25	                </ul>
    26	            </div>
    27	        @endif
    28	
    29	        {{-- Print Controls --}}
    30	        <div class="flex justify-between items-center mb-6 no-print">
    31	            <div>
    32	                <h3 class="text-lg font-bold text-gray-900 dark:text-gray-100">
    33	                    كارنيه رقم: {{ $cardData['card']->card_number }}
    34	                </h3>
    35	                <p class="text-sm text-gray-500">{{ $cardData['member']->full_name_ar }}</p>
    36	            </div>
    37	            <div class="flex gap-3">
    38	                <button onclick="window.print()"
    39	                        class="inline-flex items-center px-4 py-2 bg-primary-600 text-white rounded-lg hover:bg-primary-700 transition"
    40	                        @if(!$isEligible) title="يوجد تنبيهات — يمكنك الطباعة على مسؤوليتك" @endif>
    41	                    <x-heroicon-s-printer class="w-4 h-4 ml-2" />
    42	                    طباعة
    43	                </button>
    44	                <button wire:click="printCard"
    45	                        class="inline-flex items-center px-4 py-2 bg-green-600 text-white rounded-lg hover:bg-green-700 transition">
    46	                    <x-heroicon-s-check class="w-4 h-4 ml-2" />
    47	                    تسجيل الطباعة
    48	                </button>
    49	            </div>
    50	        </div>
    51	
    52	        {{-- Card Preview --}}
    53	        <div class="flex flex-col lg:flex-row gap-8 justify-center items-start print-area">
    54	            {{-- FRONT --}}
    55	            <div class="card-wrapper">
    56	                <p class="text-center text-sm text-gray-500 mb-2 no-print font-bold">الوجه الأمامي</p>
    57	                <div class="card-face card-front"
    58	                     style="width: 340px; height: 215px; border-radius: 12px; overflow: hidden; background: linear-gradient(135deg, #1e3a5f 0%, #2d5f8a 50%, #1e3a5f 100%); color: white; position: relative; box-shadow: 0 8px 32px rgba(0,0,0,0.3); font-family: 'Cairo', 'Tajawal', sans-serif;">
    59	
    60	                    {{-- Top Bar --}}
    61	                    <div style="background: rgba(255,255,255,0.15); padding: 6px 12px; display: flex; justify-content: space-between; align-items: center;">
    62	                        <span style="font-size: 10px; font-weight: bold;">{{ $cardData['back']['club_name_ar'] }}</span>
    63	                        <span style="font-size: 8px; opacity: 0.8;">{{ $cardData['back']['club_name_en'] }}</span>
    64	                    </div>
    65	
    66	                    <div style="display: flex; padding: 10px 12px; gap: 10px; height: calc(100% - 32px);">
    67	                        {{-- Photo --}}
    68	                        <div style="flex-shrink: 0;">
    69	                            <div style="width: 80px; height: 100px; border-radius: 8px; overflow: hidden; border: 2px solid rgba(255,255,255,0.5);">
    70	                                <img src="{{ $cardData['front']['photo_url'] }}"
    71	                                     alt="صورة العضو"
    72	                                     style="width: 100%; height: 100%; object-fit: cover;"
    73	                                     onerror="this.src='data:image/svg+xml,%3Csvg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 80 100%22%3E%3Crect fill=%22%23ccc%22 width=%2280%22 height=%22100%22/%3E%3Ctext x=%2240%22 y=%2255%22 text-anchor=%22middle%22 fill=%22%23666%22 font-size=%2212%22%3Eلا صورة%3C/text%3E%3C/svg%3E'" />
    74	                            </div>
    75	                        </div>
    76	
    77	                        {{-- Info --}}
    78	                        <div style="flex: 1; display: flex; flex-direction: column; justify-content: space-between;">
    79	                            <div>
    80	                                <div style="font-size: 13px; font-weight: bold; line-height: 1.3; margin-bottom: 2px;">{{ $cardData['front']['full_name_ar'] }}</div>
    81	                                @if($cardData['front']['full_name_en'])
    82	                                    <div style="font-size: 9px; opacity: 0.8; margin-bottom: 4px;">{{ $cardData['front']['full_name_en'] }}</div>
    83	                                @endif
    84	                                <div style="font-size: 10px; opacity: 0.9;">
    85	                                    <span style="background: rgba(255,255,255,0.2); padding: 1px 6px; border-radius: 4px;">{{ $cardData['front']['membership_type_ar'] }}</span>
    86	                                </div>
    87	                            </div>
    88	
    89	                            <div style="display: flex; justify-content: space-between; align-items: flex-end;">
    90	                                <div>
    91	                                    <div style="font-size: 9px; opacity: 0.7;">رقم العضوية</div>
    92	                                    <div style="font-size: 14px; font-weight: bold; letter-spacing: 1px;">{{ $cardData['front']['membership_number'] }}</div>
    93	                                </div>
    94	                                <div style="text-align: center;">
    95	                                    <div style="font-size: 8px; opacity: 0.7;">منذ</div>
    96	                                    <div style="font-size: 12px; font-weight: bold;">{{ $cardData['front']['since_year'] }}</div>
    97	                                </div>
    98	                            </div>
    99	                        </div>
   100	
   101	                        {{-- Status Badge --}}
   102	                        <div style="position: absolute; top: 40px; left: 10px;">
   103	                            <div style="background: {{ $cardData['front']['status_color'] }}; color: white; padding: 2px 8px; border-radius: 10px; font-size: 8px; font-weight: bold;">
   104	                                {{ $cardData['front']['status_ar'] }}
   105	                            </div>
   106	                        </div>
   107	                    </div>
   108	
   109	                    {{-- Bottom Strip --}}
   110	                    <div style="position: absolute; bottom: 0; left: 0; right: 0; background: rgba(0,0,0,0.3); padding: 3px 12px; display: flex; justify-content: space-between; font-size: 7px; opacity: 0.8;">
   111	                        <span>إصدار: {{ $cardData['front']['issue_date'] }}</span>
   112	                        <span>انتهاء: {{ $cardData['front']['expiry_date'] }}</span>
   113	                    </div>
   114	                </div>
   115	            </div>
   116	
   117	            {{-- BACK --}}
   118	            <div class="card-wrapper">
   119	                <p class="text-center text-sm text-gray-500 mb-2 no-print font-bold">الوجه الخلفي</p>
   120	                <div class="card-face card-back"
   121	                     style="width: 340px; height: 215px; border-radius: 12px; overflow: hidden; background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%); color: #333; position: relative; box-shadow: 0 8px 32px rgba(0,0,0,0.15); font-family: 'Cairo', 'Tajawal', sans-serif;">
   122	
   123	                    {{-- Header --}}
   124	                    <div style="background: #1e3a5f; color: white; padding: 5px 12px; text-align: center; font-size: 10px; font-weight: bold;">
   125	                        {{ $cardData['back']['club_name_ar'] }}
   126	                    </div>
   127	
   128	                    <div style="padding: 8px 12px; font-size: 9px; line-height: 1.6;">
   129	                        <div style="display: grid; grid-template-columns: 1fr 1fr; gap: 3px 12px;">
   130	                            <div>
   131	                                <span style="opacity: 0.6;">الرقم القومي:</span><br>
   132	                                <span style="font-weight: bold; font-size: 10px;">{{ $cardData['back']['national_id'] }}</span>
   133	                            </div>
   134	                            <div>
   135	                                <span style="opacity: 0.6;">فصيلة الدم:</span><br>
   136	                                <span style="font-weight: bold; font-size: 10px;">{{ $cardData['back']['blood_type'] }}</span>
   137	                            </div>
   138	                            <div style="grid-column: 1 / -1;">
   139	                                <span style="opacity: 0.6;">للطوارئ:</span>
   140	                                <span style="font-weight: bold;">{{ $cardData['back']['emergency_contact_name'] }} — {{ $cardData['back']['emergency_contact_phone'] }}</span>
   141	                            </div>
   142	                        </div>
   143	
   144	                        <div style="border-top: 1px dashed #ccc; margin: 6px 0; padding-top: 4px; display: flex; justify-content: space-between; align-items: center;">
   145	                            <div>
   146	                                <div style="opacity: 0.6; font-size: 8px;">رقم الكارنيه</div>
   147	                                <div style="font-weight: bold; font-size: 10px;">{{ $cardData['back']['card_number'] }}</div>
   148	                            </div>
   149	                            <div style="text-align: center;">
   150	                                {!! $cardData['front']['qr_code_svg'] !!}
   151	                            </div>
   152	                        </div>
   153	                    </div>
   154	
   155	                    {{-- Footer --}}
   156	                    <div style="position: absolute; bottom: 0; left: 0; right: 0; background: #1e3a5f; color: white; padding: 3px 12px; font-size: 7px; text-align: center; opacity: 0.9;">
   157	                        {{ $cardData['back']['club_address'] }} | {{ $cardData['back']['club_phone'] }}
   158	                    </div>
   159	                </div>
   160	            </div>
   161	        </div>
   162	
   163	        <style>
   164	            @media print {
   165	                .no-print, .fi-header, .fi-sidebar, .fi-topbar, .fi-main-ctn > :not(.print-area) {
   166	                    display: none !important;
   167	                }
   168	                .print-area {
   169	                    display: flex !important;
   170	                    justify-content: center;
   171	                    gap: 20px;
   172	                }
   173	                .card-face {
   174	                    -webkit-print-color-adjust: exact;
   175	                    print-color-adjust: exact;
   176	                }
   177	                body { background: white !important; }
   178	            }
   179	        </style>
   180	    @endif
   181	</x-filament-panels::page>│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [456]: resources/views/filament/pages/card-preview.blade.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [457/494]: resources/views/filament/pages/document-checklist.blade.php
│ LANGUAGE: php | LINES: 140 | SIZE: 9252 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<x-filament-panels::page>
     2	    {{-- Member Selector Form --}}
     3	    <div class="mb-6">
     4	        {{ $this->form }}
     5	    </div>
     6	
     7	    @if($memberInfo)
     8	        {{-- Member Info Card --}}
     9	        <div class="bg-white dark:bg-gray-800 rounded-xl border border-gray-200 dark:border-gray-700 p-4 mb-6">
    10	            <div class="flex justify-between items-center">
    11	                <div>
    12	                    <h3 class="text-lg font-bold text-gray-900 dark:text-gray-100">{{ $memberInfo['name'] }}</h3>
    13	                    <p class="text-sm text-gray-500 dark:text-gray-400">
    14	                        رقم العضوية: <span class="font-mono">{{ $memberInfo['membership_number'] }}</span>
    15	                        | النوع: {{ $memberInfo['type'] }}
    16	                        | الحالة: {{ $memberInfo['status'] }}
    17	                    </p>
    18	                </div>
    19	                @if($checklist)
    20	                    <div class="text-center">
    21	                        <div class="text-2xl font-bold {{ $checklist['completion_percentage'] >= 100 ? 'text-green-600' : ($checklist['completion_percentage'] >= 50 ? 'text-yellow-600' : 'text-red-600') }}">
    22	                            {{ $checklist['completion_percentage'] }}%
    23	                        </div>
    24	                        <div class="text-xs text-gray-500">اكتمال المستندات</div>
    25	                    </div>
    26	                @endif
    27	            </div>
    28	
    29	            {{-- Progress Bar --}}
    30	            @if($checklist)
    31	                <div class="mt-3">
    32	                    <div class="flex justify-between text-xs text-gray-500 mb-1">
    33	                        <span>{{ $checklist['total_completed'] }} من {{ $checklist['total_required'] }} مستند مطلوب</span>
    34	                        <span>{{ $checklist['is_complete'] ? '✅ مكتمل' : '⏳ غير مكتمل' }}</span>
    35	                    </div>
    36	                    <div class="w-full bg-gray-200 dark:bg-gray-700 rounded-full h-3">
    37	                        <div class="h-3 rounded-full transition-all duration-500 {{ $checklist['completion_percentage'] >= 100 ? 'bg-green-500' : ($checklist['completion_percentage'] >= 50 ? 'bg-yellow-500' : 'bg-red-500') }}"
    38	                             style="width: {{ $checklist['completion_percentage'] }}%"></div>
    39	                    </div>
    40	                </div>
    41	            @endif
    42	        </div>
    43	    @endif
    44	
    45	    @if($checklist && count($checklist['checklist'] ?? []) > 0)
    46	        {{-- Document Checklist Grid --}}
    47	        <div class="space-y-3">
    48	            @foreach($checklist['checklist'] as $item)
    49	                <div class="bg-white dark:bg-gray-800 rounded-xl border {{ $item['status'] === 'missing' && $item['is_mandatory'] ? 'border-red-300 dark:border-red-700' : 'border-gray-200 dark:border-gray-700' }} p-4 transition hover:shadow-md">
    50	                    <div class="flex items-center justify-between gap-4">
    51	                        {{-- Status Icon & Document Name --}}
    52	                        <div class="flex items-center gap-3 flex-1">
    53	                            <div class="flex-shrink-0">
    54	                                @switch($item['status'])
    55	                                    @case('verified')
    56	                                        <div class="w-10 h-10 bg-green-100 dark:bg-green-900/30 rounded-full flex items-center justify-center">
    57	                                            <x-heroicon-s-check-circle class="w-6 h-6 text-green-600" />
    58	                                        </div>
    59	                                        @break
    60	                                    @case('uploaded')
    61	                                        <div class="w-10 h-10 bg-yellow-100 dark:bg-yellow-900/30 rounded-full flex items-center justify-center">
    62	                                            <x-heroicon-s-clock class="w-6 h-6 text-yellow-600" />
    63	                                        </div>
    64	                                        @break
    65	                                    @case('expired')
    66	                                        <div class="w-10 h-10 bg-red-100 dark:bg-red-900/30 rounded-full flex items-center justify-center">
    67	                                            <x-heroicon-s-exclamation-triangle class="w-6 h-6 text-red-600" />
    68	                                        </div>
    69	                                        @break
    70	                                    @default
    71	                                        <div class="w-10 h-10 bg-red-100 dark:bg-red-900/30 rounded-full flex items-center justify-center">
    72	                                            <x-heroicon-s-x-circle class="w-6 h-6 text-red-500" />
    73	                                        </div>
    74	                                @endswitch
    75	                            </div>
    76	
    77	                            <div>
    78	                                <div class="font-bold text-gray-900 dark:text-gray-100 flex items-center gap-2">
    79	                                    {{ $item['document_type_name_ar'] }}
    80	                                    @if($item['is_mandatory'])
    81	                                        <span class="text-xs bg-red-100 text-red-700 dark:bg-red-900/30 dark:text-red-300 px-2 py-0.5 rounded-full">إلزامي</span>
    82	                                    @else
    83	                                        <span class="text-xs bg-gray-100 text-gray-600 dark:bg-gray-700 dark:text-gray-400 px-2 py-0.5 rounded-full">اختياري</span>
    84	                                    @endif
    85	                                </div>
    86	                                <div class="text-sm text-gray-500 dark:text-gray-400">
    87	                                    {{ $item['status_label'] }}
    88	                                    @if($item['accepted_extensions'])
    89	                                        <span class="mx-1">•</span>
    90	                                        <span class="text-xs">{{ $item['accepted_extensions'] }}</span>
    91	                                    @endif
    92	                                </div>
    93	                            </div>
    94	                        </div>
    95	
    96	                        {{-- Actions --}}
    97	                        <div class="flex items-center gap-2 flex-shrink-0">
    98	                            @if($item['existing_document'])
    99	                                {{-- View Button --}}
   100	                                <a href="{{ asset('storage/' . $item['existing_document']->file_path) }}"
   101	                                   target="_blank"
   102	                                   class="inline-flex items-center px-3 py-1.5 bg-blue-50 text-blue-700 dark:bg-blue-900/30 dark:text-blue-300 rounded-lg text-sm hover:bg-blue-100 transition">
   103	                                    <x-heroicon-s-eye class="w-4 h-4 ml-1" />
   104	                                    عرض
   105	                                </a>
   106	
   107	                                {{-- Verify Button (if not verified) --}}
   108	                                @if(!$item['existing_document']->is_verified)
   109	                                    <button wire:click="verifyDocument({{ $item['existing_document']->id }})"
   110	                                            class="inline-flex items-center px-3 py-1.5 bg-green-50 text-green-700 dark:bg-green-900/30 dark:text-green-300 rounded-lg text-sm hover:bg-green-100 transition">
   111	                                        <x-heroicon-s-check-badge class="w-4 h-4 ml-1" />
   112	                                        تحقق
   113	                                    </button>
   114	                                @endif
   115	                            @endif
   116	
   117	                            {{-- Upload Button --}}
   118	                            <a href="{{ route('filament.admin.resources.member-documents.create', ['member_id' => $memberInfo['id'], 'document_type_id' => $item['document_type_id']]) }}"
   119	                               class="inline-flex items-center px-3 py-1.5 bg-primary-50 text-primary-700 dark:bg-primary-900/30 dark:text-primary-300 rounded-lg text-sm hover:bg-primary-100 transition">
   120	                                <x-heroicon-s-arrow-up-tray class="w-4 h-4 ml-1" />
   121	                                {{ $item['existing_document'] ? 'استبدال' : 'رفع' }}
   122	                            </a>
   123	                        </div>
   124	                    </div>
   125	                </div>
   126	            @endforeach
   127	        </div>
   128	    @elseif($selectedMemberId && $checklist && count($checklist['checklist'] ?? []) === 0)
   129	        <div class="text-center py-12">
   130	            <x-heroicon-o-document-check class="w-16 h-16 mx-auto text-gray-400 mb-4" />
   131	            <h3 class="text-lg font-medium text-gray-900 dark:text-gray-100 mb-2">لا توجد متطلبات مستندات</h3>
   132	            <p class="text-gray-500 dark:text-gray-400">لم يتم تعريف متطلبات مستندات لهذه المرحلة.</p>
   133	        </div>
   134	    @elseif(!$selectedMemberId)
   135	        <div class="text-center py-12">
   136	            <x-heroicon-o-user class="w-16 h-16 mx-auto text-gray-400 mb-4" />
   137	            <h3 class="text-lg font-medium text-gray-900 dark:text-gray-100 mb-2">اختر عضواً</h3>
   138	            <p class="text-gray-500 dark:text-gray-400">يرجى اختيار عضو من القائمة أعلاه لعرض قائمة المستندات المطلوبة.</p>
   139	        </div>
   140	    @endif
   141	</x-filament-panels::page>│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [457]: resources/views/filament/pages/document-checklist.blade.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [458/494]: resources/views/filament/pages/reports/aging-report.blade.php
│ LANGUAGE: php | LINES: 16 | SIZE: 1030 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<x-filament-panels::page>
     2	    {{-- Aging Brackets Summary --}}
     3	    <x-filament::section class="mb-6">
     4	        <x-slot name="heading">ملخص أعمار الديون</x-slot>
     5	        <div class="grid grid-cols-2 md:grid-cols-5 gap-4">
     6	            @foreach ($agingBrackets as $bracket)
     7	                <div class="text-center p-4 rounded-xl border {{ $loop->last ? 'border-danger-300 bg-danger-50 dark:bg-danger-950 dark:border-danger-800' : 'border-gray-200 bg-gray-50 dark:bg-gray-800 dark:border-gray-700' }}">
     8	                    <p class="text-xs text-gray-500 dark:text-gray-400 mb-1">{{ $bracket['bracket'] }} يوم</p>
     9	                    <p class="text-xl font-bold {{ $loop->last ? 'text-danger-600' : 'text-gray-900 dark:text-white' }}">{{ number_format($bracket['total'], 2) }}</p>
    10	                    <p class="text-xs text-gray-400 mt-1">{{ $bracket['count'] }} إيصال</p>
    11	                </div>
    12	            @endforeach
    13	        </div>
    14	    </x-filament::section>
    15	
    16	    {{ $this->table }}
    17	</x-filament-panels::page>│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [458]: resources/views/filament/pages/reports/aging-report.blade.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [459/494]: resources/views/filament/pages/reports/churn-report.blade.php
│ LANGUAGE: php | LINES: 83 | SIZE: 4884 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<x-filament-panels::page>
     2	    <div class="flex gap-3 mb-6">
     3	        <x-filament::button wire:click="setMonths(6)" :color="$months === 6 ? 'primary' : 'gray'" size="sm">
     4	            6 أشهر
     5	        </x-filament::button>
     6	        <x-filament::button wire:click="setMonths(12)" :color="$months === 12 ? 'primary' : 'gray'" size="sm">
     7	            12 شهر
     8	        </x-filament::button>
     9	        <x-filament::button wire:click="setMonths(24)" :color="$months === 24 ? 'primary' : 'gray'" size="sm">
    10	            24 شهر
    11	        </x-filament::button>
    12	        <div class="flex-1"></div>
    13	        <x-filament::button wire:click="exportPdf" color="danger" icon="heroicon-o-document-arrow-down" size="sm">
    14	            تصدير PDF
    15	        </x-filament::button>
    16	    </div>
    17	
    18	    <x-filament::section>
    19	        <x-slot name="heading">تقرير تسرب الأعضاء (آخر {{ $months }} شهر)</x-slot>
    20	        <div class="overflow-x-auto">
    21	            <table class="w-full text-sm">
    22	                <thead>
    23	                    <tr class="border-b dark:border-gray-700 bg-gray-50 dark:bg-gray-800">
    24	                        <th class="text-right p-3 font-medium">الشهر</th>
    25	                        <th class="text-center p-3 font-medium">أعضاء جدد</th>
    26	                        <th class="text-center p-3 font-medium">استقالة</th>
    27	                        <th class="text-center p-3 font-medium">إلغاء</th>
    28	                        <th class="text-center p-3 font-medium">انتهاء</th>
    29	                        <th class="text-center p-3 font-medium">صافي التغيير</th>
    30	                    </tr>
    31	                </thead>
    32	                <tbody>
    33	                    @foreach ($churnData as $row)
    34	                        <tr class="border-b dark:border-gray-700 hover:bg-gray-50 dark:hover:bg-gray-800/50">
    35	                            <td class="p-3 font-medium">{{ $row['month'] }}</td>
    36	                            <td class="p-3 text-center">
    37	                                <x-filament::badge color="success">+{{ $row['new'] }}</x-filament::badge>
    38	                            </td>
    39	                            <td class="p-3 text-center">
    40	                                @if ($row['resigned'] > 0)
    41	                                    <x-filament::badge color="danger">{{ $row['resigned'] }}</x-filament::badge>
    42	                                @else
    43	                                    <span class="text-gray-400">0</span>
    44	                                @endif
    45	                            </td>
    46	                            <td class="p-3 text-center">
    47	                                @if ($row['cancelled'] > 0)
    48	                                    <x-filament::badge color="danger">{{ $row['cancelled'] }}</x-filament::badge>
    49	                                @else
    50	                                    <span class="text-gray-400">0</span>
    51	                                @endif
    52	                            </td>
    53	                            <td class="p-3 text-center">
    54	                                @if ($row['expired'] > 0)
    55	                                    <x-filament::badge color="warning">{{ $row['expired'] }}</x-filament::badge>
    56	                                @else
    57	                                    <span class="text-gray-400">0</span>
    58	                                @endif
    59	                            </td>
    60	                            <td class="p-3 text-center font-bold {{ $row['net_change'] >= 0 ? 'text-success-600' : 'text-danger-600' }}">
    61	                                {{ $row['net_change'] >= 0 ? '+' : '' }}{{ $row['net_change'] }}
    62	                            </td>
    63	                        </tr>
    64	                    @endforeach
    65	                </tbody>
    66	                <tfoot>
    67	                    <tr class="bg-gray-100 dark:bg-gray-800 font-bold">
    68	                        <td class="p-3">الإجمالي</td>
    69	                        <td class="p-3 text-center text-success-600">+{{ array_sum(array_column($churnData, 'new')) }}</td>
    70	                        <td class="p-3 text-center text-danger-600">{{ array_sum(array_column($churnData, 'resigned')) }}</td>
    71	                        <td class="p-3 text-center text-danger-600">{{ array_sum(array_column($churnData, 'cancelled')) }}</td>
    72	                        <td class="p-3 text-center text-warning-600">{{ array_sum(array_column($churnData, 'expired')) }}</td>
    73	                        @php
    74	                            $totalNet = array_sum(array_column($churnData, 'net_change'));
    75	                        @endphp
    76	                        <td class="p-3 text-center {{ $totalNet >= 0 ? 'text-success-600' : 'text-danger-600' }}">
    77	                            {{ $totalNet >= 0 ? '+' : '' }}{{ $totalNet }}
    78	                        </td>
    79	                    </tr>
    80	                </tfoot>
    81	            </table>
    82	        </div>
    83	    </x-filament::section>
    84	</x-filament-panels::page>│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [459]: resources/views/filament/pages/reports/churn-report.blade.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [460/494]: resources/views/filament/pages/reports/financial-report.blade.php
│ LANGUAGE: php | LINES: 94 | SIZE: 4637 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<x-filament-panels::page>
     2	    {{-- Collection Summary --}}
     3	    <div class="grid grid-cols-2 md:grid-cols-4 gap-4 mb-6">
     4	        <x-filament::section>
     5	            <div class="text-center">
     6	                <p class="text-sm text-gray-500 dark:text-gray-400">إجمالي المحصّل</p>
     7	                <p class="text-2xl font-bold text-success-600">{{ number_format($collectionSummary['total_collected'] ?? 0, 2) }} ج.م</p>
     8	            </div>
     9	        </x-filament::section>
    10	
    11	        <x-filament::section>
    12	            <div class="text-center">
    13	                <p class="text-sm text-gray-500 dark:text-gray-400">إجمالي المستحق</p>
    14	                <p class="text-2xl font-bold text-danger-600">{{ number_format($collectionSummary['total_outstanding'] ?? 0, 2) }} ج.م</p>
    15	            </div>
    16	        </x-filament::section>
    17	
    18	        <x-filament::section>
    19	            <div class="text-center">
    20	                <p class="text-sm text-gray-500 dark:text-gray-400">نسبة التحصيل</p>
    21	                <p class="text-2xl font-bold text-primary-600">{{ $collectionSummary['collection_rate'] ?? 0 }}%</p>
    22	            </div>
    23	        </x-filament::section>
    24	
    25	        <x-filament::section>
    26	            <div class="text-center">
    27	                <p class="text-sm text-gray-500 dark:text-gray-400">عدد الإيصالات</p>
    28	                <p class="text-2xl font-bold text-info-600">{{ number_format($collectionSummary['receipt_count'] ?? 0) }}</p>
    29	            </div>
    30	        </x-filament::section>
    31	    </div>
    32	
    33	    {{-- Revenue Breakdown --}}
    34	    <div class="grid grid-cols-1 md:grid-cols-2 gap-4 mb-6">
    35	        <x-filament::section>
    36	            <x-slot name="heading">الإيرادات حسب الفئة</x-slot>
    37	            <div class="space-y-2">
    38	                @forelse ($revenueByCategory as $category)
    39	                    <div class="flex items-center justify-between p-2 rounded-lg bg-gray-50 dark:bg-gray-800">
    40	                        <span class="text-sm">{{ $category['label'] }}</span>
    41	                        <x-filament::badge color="success">{{ number_format($category['total'], 2) }} ج.م</x-filament::badge>
    42	                    </div>
    43	                @empty
    44	                    <p class="text-sm text-gray-500">لا توجد بيانات</p>
    45	                @endforelse
    46	            </div>
    47	        </x-filament::section>
    48	
    49	        <x-filament::section>
    50	            <x-slot name="heading">الإيرادات حسب طريقة الدفع</x-slot>
    51	            <div class="space-y-2">
    52	                @forelse ($revenueByPaymentMethod as $method)
    53	                    <div class="flex items-center justify-between p-2 rounded-lg bg-gray-50 dark:bg-gray-800">
    54	                        <span class="text-sm">{{ $method['method'] }} ({{ $method['count'] }} إيصال)</span>
    55	                        <x-filament::badge color="info">{{ number_format($method['total'], 2) }} ج.م</x-filament::badge>
    56	                    </div>
    57	                @empty
    58	                    <p class="text-sm text-gray-500">لا توجد بيانات</p>
    59	                @endforelse
    60	            </div>
    61	        </x-filament::section>
    62	    </div>
    63	
    64	    {{-- Aging Summary --}}
    65	    <x-filament::section class="mb-6">
    66	        <x-slot name="heading">ملخص أعمار الديون</x-slot>
    67	        <div class="grid grid-cols-5 gap-3">
    68	            @foreach ($agingReport as $bracket)
    69	                <div class="text-center p-3 rounded-lg {{ $loop->last ? 'bg-danger-50 dark:bg-danger-950' : 'bg-gray-50 dark:bg-gray-800' }}">
    70	                    <p class="text-xs text-gray-500">{{ $bracket['bracket'] }} يوم</p>
    71	                    <p class="text-lg font-bold {{ $loop->last ? 'text-danger-600' : '' }}">{{ number_format($bracket['total'], 2) }}</p>
    72	                    <p class="text-xs text-gray-400">{{ $bracket['count'] }} إيصال</p>
    73	                </div>
    74	            @endforeach
    75	        </div>
    76	    </x-filament::section>
    77	
    78	    {{-- Filters --}}
    79	    <x-filament::section collapsible collapsed>
    80	        <x-slot name="heading">فلاتر البحث</x-slot>
    81	        <form wire:submit="applyFilters">
    82	            {{ $this->filtersForm }}
    83	            <div class="flex gap-3 mt-4">
    84	                <x-filament::button type="submit" icon="heroicon-m-funnel">
    85	                    تطبيق الفلاتر
    86	                </x-filament::button>
    87	                <x-filament::button color="gray" wire:click="resetFilters" icon="heroicon-m-x-mark">
    88	                    إعادة تعيين
    89	                </x-filament::button>
    90	            </div>
    91	        </form>
    92	    </x-filament::section>
    93	
    94	    {{ $this->table }}
    95	</x-filament-panels::page>│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [460]: resources/views/filament/pages/reports/financial-report.blade.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [461/494]: resources/views/filament/pages/reports/installment-report.blade.php
│ LANGUAGE: php | LINES: 17 | SIZE: 723 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<x-filament-panels::page>
     2	    <x-filament::section collapsible collapsed class="mb-6">
     3	        <x-slot name="heading">فلاتر البحث</x-slot>
     4	        <form wire:submit="applyFilters">
     5	            {{ $this->filtersForm }}
     6	            <div class="flex gap-3 mt-4">
     7	                <x-filament::button type="submit" icon="heroicon-m-funnel">
     8	                    تطبيق الفلاتر
     9	                </x-filament::button>
    10	                <x-filament::button color="gray" wire:click="resetFilters" icon="heroicon-m-x-mark">
    11	                    إعادة تعيين
    12	                </x-filament::button>
    13	            </div>
    14	        </form>
    15	    </x-filament::section>
    16	
    17	    {{ $this->table }}
    18	</x-filament-panels::page>│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [461]: resources/views/filament/pages/reports/installment-report.blade.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [462/494]: resources/views/filament/pages/reports/membership-report.blade.php
│ LANGUAGE: php | LINES: 68 | SIZE: 3220 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<x-filament-panels::page>
     2	    {{-- Summary Cards --}}
     3	    <div class="grid grid-cols-1 md:grid-cols-3 gap-4 mb-6">
     4	        {{-- By Membership Type --}}
     5	        <x-filament::section>
     6	            <x-slot name="heading">ملخص حسب نوع العضوية</x-slot>
     7	            <div class="space-y-2">
     8	                @forelse ($summaryByType as $type)
     9	                    <div class="flex items-center justify-between p-2 rounded-lg bg-gray-50 dark:bg-gray-800">
    10	                        <span class="font-medium text-sm">{{ $type['type_name'] }}</span>
    11	                        <div class="flex gap-2">
    12	                            <x-filament::badge color="primary">{{ $type['total'] }} إجمالي</x-filament::badge>
    13	                            <x-filament::badge color="success">{{ $type['active'] }} نشط</x-filament::badge>
    14	                        </div>
    15	                    </div>
    16	                @empty
    17	                    <p class="text-sm text-gray-500">لا توجد بيانات</p>
    18	                @endforelse
    19	            </div>
    20	        </x-filament::section>
    21	
    22	        {{-- Age Distribution --}}
    23	        <x-filament::section>
    24	            <x-slot name="heading">توزيع الأعمار (أعضاء نشطون)</x-slot>
    25	            <div class="space-y-2">
    26	                @foreach ($ageDistribution as $bracket)
    27	                    <div class="flex items-center justify-between p-2 rounded-lg bg-gray-50 dark:bg-gray-800">
    28	                        <span class="text-sm">{{ $bracket['bracket'] }} سنة</span>
    29	                        <x-filament::badge color="info">{{ $bracket['count'] }}</x-filament::badge>
    30	                    </div>
    31	                @endforeach
    32	            </div>
    33	        </x-filament::section>
    34	
    35	        {{-- Gender Distribution --}}
    36	        <x-filament::section>
    37	            <x-slot name="heading">توزيع الجنس (أعضاء نشطون)</x-slot>
    38	            <div class="space-y-2">
    39	                @forelse ($genderDistribution as $gender)
    40	                    <div class="flex items-center justify-between p-2 rounded-lg bg-gray-50 dark:bg-gray-800">
    41	                        <span class="text-sm">{{ $gender['gender'] }}</span>
    42	                        <x-filament::badge color="primary">{{ $gender['count'] }}</x-filament::badge>
    43	                    </div>
    44	                @empty
    45	                    <p class="text-sm text-gray-500">لا توجد بيانات</p>
    46	                @endforelse
    47	            </div>
    48	        </x-filament::section>
    49	    </div>
    50	
    51	    {{-- Filters --}}
    52	    <x-filament::section collapsible collapsed>
    53	        <x-slot name="heading">فلاتر البحث</x-slot>
    54	        <form wire:submit="applyFilters">
    55	            {{ $this->filtersForm }}
    56	            <div class="flex gap-3 mt-4">
    57	                <x-filament::button type="submit" icon="heroicon-m-funnel">
    58	                    تطبيق الفلاتر
    59	                </x-filament::button>
    60	                <x-filament::button color="gray" wire:click="resetFilters" icon="heroicon-m-x-mark">
    61	                    إعادة تعيين
    62	                </x-filament::button>
    63	            </div>
    64	        </form>
    65	    </x-filament::section>
    66	
    67	    {{-- Table --}}
    68	    {{ $this->table }}
    69	</x-filament-panels::page>│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [462]: resources/views/filament/pages/reports/membership-report.blade.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [463/494]: resources/views/filament/pages/reports/subscription-report.blade.php
│ LANGUAGE: php | LINES: 90 | SIZE: 4604 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<x-filament-panels::page>
     2	    {{-- Summary By Plan --}}
     3	    <x-filament::section class="mb-6">
     4	        <x-slot name="heading">ملخص الاشتراكات حسب الخطة</x-slot>
     5	        <div class="overflow-x-auto">
     6	            <table class="w-full text-sm">
     7	                <thead>
     8	                    <tr class="border-b dark:border-gray-700">
     9	                        <th class="text-right p-2 font-medium text-gray-600 dark:text-gray-400">الخطة</th>
    10	                        <th class="text-center p-2 font-medium text-gray-600 dark:text-gray-400">الإجمالي</th>
    11	                        <th class="text-center p-2 font-medium text-gray-600 dark:text-gray-400">نشط</th>
    12	                        <th class="text-center p-2 font-medium text-gray-600 dark:text-gray-400">منتهي</th>
    13	                        <th class="text-center p-2 font-medium text-gray-600 dark:text-gray-400">ملغي</th>
    14	                    </tr>
    15	                </thead>
    16	                <tbody>
    17	                    @forelse ($summaryByPlan as $plan)
    18	                        <tr class="border-b dark:border-gray-700">
    19	                            <td class="p-2 font-medium">{{ $plan['plan_name'] }}</td>
    20	                            <td class="p-2 text-center">
    21	                                <x-filament::badge color="primary">{{ $plan['total'] }}</x-filament::badge>
    22	                            </td>
    23	                            <td class="p-2 text-center">
    24	                                <x-filament::badge color="success">{{ $plan['active'] }}</x-filament::badge>
    25	                            </td>
    26	                            <td class="p-2 text-center">
    27	                                <x-filament::badge color="danger">{{ $plan['expired'] }}</x-filament::badge>
    28	                            </td>
    29	                            <td class="p-2 text-center">
    30	                                <x-filament::badge color="gray">{{ $plan['cancelled'] }}</x-filament::badge>
    31	                            </td>
    32	                        </tr>
    33	                    @empty
    34	                        <tr>
    35	                            <td colspan="5" class="p-4 text-center text-gray-500">لا توجد بيانات</td>
    36	                        </tr>
    37	                    @endforelse
    38	                </tbody>
    39	            </table>
    40	        </div>
    41	    </x-filament::section>
    42	
    43	    {{-- Renewal Rate --}}
    44	    <x-filament::section collapsible collapsed class="mb-6">
    45	        <x-slot name="heading">معدل التجديد (آخر 12 شهر)</x-slot>
    46	        <div class="overflow-x-auto">
    47	            <table class="w-full text-sm">
    48	                <thead>
    49	                    <tr class="border-b dark:border-gray-700">
    50	                        <th class="text-right p-2">الشهر</th>
    51	                        <th class="text-center p-2">انتهت</th>
    52	                        <th class="text-center p-2">تم تجديدها</th>
    53	                        <th class="text-center p-2">نسبة التجديد</th>
    54	                    </tr>
    55	                </thead>
    56	                <tbody>
    57	                    @foreach ($renewalRate as $row)
    58	                        <tr class="border-b dark:border-gray-700">
    59	                            <td class="p-2">{{ $row['month'] }}</td>
    60	                            <td class="p-2 text-center">{{ $row['expired'] }}</td>
    61	                            <td class="p-2 text-center">{{ $row['renewed'] }}</td>
    62	                            <td class="p-2 text-center">
    63	                                <x-filament::badge :color="$row['rate'] >= 80 ? 'success' : ($row['rate'] >= 50 ? 'warning' : 'danger')">
    64	                                    {{ $row['rate'] }}%
    65	                                </x-filament::badge>
    66	                            </td>
    67	                        </tr>
    68	                    @endforeach
    69	                </tbody>
    70	            </table>
    71	        </div>
    72	    </x-filament::section>
    73	
    74	    {{-- Filters --}}
    75	    <x-filament::section collapsible collapsed>
    76	        <x-slot name="heading">فلاتر البحث</x-slot>
    77	        <form wire:submit="applyFilters">
    78	            {{ $this->filtersForm }}
    79	            <div class="flex gap-3 mt-4">
    80	                <x-filament::button type="submit" icon="heroicon-m-funnel">
    81	                    تطبيق الفلاتر
    82	                </x-filament::button>
    83	                <x-filament::button color="gray" wire:click="resetFilters" icon="heroicon-m-x-mark">
    84	                    إعادة تعيين
    85	                </x-filament::button>
    86	            </div>
    87	        </form>
    88	    </x-filament::section>
    89	
    90	    {{ $this->table }}
    91	</x-filament-panels::page>│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [463]: resources/views/filament/pages/reports/subscription-report.blade.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [464/494]: resources/views/filament/pages/reports/top-paying-members.blade.php
│ LANGUAGE: php | LINES: 68 | SIZE: 3705 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<x-filament-panels::page>
     2	    <x-filament::section class="mb-6">
     3	        <x-slot name="heading">فلاتر البحث</x-slot>
     4	        <form wire:submit="applyFilters">
     5	            {{ $this->form }}
     6	            <div class="flex gap-3 mt-4">
     7	                <x-filament::button type="submit" icon="heroicon-m-funnel">
     8	                    بحث
     9	                </x-filament::button>
    10	                <x-filament::button color="danger" wire:click="exportPdf" icon="heroicon-o-document-arrow-down">
    11	                    تصدير PDF
    12	                </x-filament::button>
    13	            </div>
    14	        </form>
    15	    </x-filament::section>
    16	
    17	    <x-filament::section>
    18	        <x-slot name="heading">أعلى {{ count($topMembers) }} أعضاء سداداً</x-slot>
    19	        <div class="overflow-x-auto">
    20	            <table class="w-full text-sm">
    21	                <thead>
    22	                    <tr class="border-b dark:border-gray-700 bg-gray-50 dark:bg-gray-800">
    23	                        <th class="text-right p-3 font-medium">#</th>
    24	                        <th class="text-right p-3 font-medium">رقم العضوية</th>
    25	                        <th class="text-right p-3 font-medium">اسم العضو</th>
    26	                        <th class="text-center p-3 font-medium">عدد الإيصالات</th>
    27	                        <th class="text-left p-3 font-medium">إجمالي المدفوعات</th>
    28	                    </tr>
    29	                </thead>
    30	                <tbody>
    31	                    @forelse ($topMembers as $index => $member)
    32	                        <tr class="border-b dark:border-gray-700 hover:bg-gray-50 dark:hover:bg-gray-800/50">
    33	                            <td class="p-3">
    34	                                @if ($index < 3)
    35	                                    <span class="text-lg">{{ ['🥇', '🥈', '🥉'][$index] }}</span>
    36	                                @else
    37	                                    <span class="text-gray-500">{{ $index + 1 }}</span>
    38	                                @endif
    39	                            </td>
    40	                            <td class="p-3">
    41	                                <x-filament::badge color="primary">{{ $member['membership_number'] }}</x-filament::badge>
    42	                            </td>
    43	                            <td class="p-3 font-medium">{{ $member['member_name'] }}</td>
    44	                            <td class="p-3 text-center">
    45	                                <x-filament::badge color="gray">{{ $member['receipt_count'] }}</x-filament::badge>
    46	                            </td>
    47	                            <td class="p-3 font-bold text-success-600">
    48	                                {{ number_format($member['total_paid'], 2) }} ج.م
    49	                            </td>
    50	                        </tr>
    51	                    @empty
    52	                        <tr>
    53	                            <td colspan="5" class="p-6 text-center text-gray-500">لا توجد بيانات للفترة المحددة</td>
    54	                        </tr>
    55	                    @endforelse
    56	                </tbody>
    57	                @if (count($topMembers) > 0)
    58	                    <tfoot>
    59	                        <tr class="bg-gray-100 dark:bg-gray-800 font-bold">
    60	                            <td colspan="3" class="p-3">الإجمالي</td>
    61	                            <td class="p-3 text-center">{{ array_sum(array_column($topMembers, 'receipt_count')) }}</td>
    62	                            <td class="p-3 text-success-600">{{ number_format(array_sum(array_column($topMembers, 'total_paid')), 2) }} ج.م</td>
    63	                        </tr>
    64	                    </tfoot>
    65	                @endif
    66	            </table>
    67	        </div>
    68	    </x-filament::section>
    69	</x-filament-panels::page>│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [464]: resources/views/filament/pages/reports/top-paying-members.blade.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [465/494]: resources/views/filament/pages/reports/violations-report.blade.php
│ LANGUAGE: php | LINES: 120 | SIZE: 6005 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<x-filament-panels::page>
     2	    {{-- Violations Summary --}}
     3	    <div class="grid grid-cols-1 md:grid-cols-2 gap-4 mb-6">
     4	        {{-- By Type --}}
     5	        <x-filament::section>
     6	            <x-slot name="heading">المخالفات حسب النوع</x-slot>
     7	            <div class="space-y-2">
     8	                @forelse ($byType as $type)
     9	                    <div class="flex items-center justify-between p-2 rounded-lg bg-gray-50 dark:bg-gray-800">
    10	                        <span class="text-sm">{{ $type['type'] }}</span>
    11	                        <x-filament::badge color="warning">{{ $type['count'] }}</x-filament::badge>
    12	                    </div>
    13	                @empty
    14	                    <p class="text-sm text-gray-500">لا توجد بيانات</p>
    15	                @endforelse
    16	            </div>
    17	        </x-filament::section>
    18	
    19	        {{-- By Severity --}}
    20	        <x-filament::section>
    21	            <x-slot name="heading">المخالفات حسب الخطورة</x-slot>
    22	            <div class="space-y-2">
    23	                @forelse ($bySeverity as $severity)
    24	                    <div class="flex items-center justify-between p-2 rounded-lg bg-gray-50 dark:bg-gray-800">
    25	                        <span class="text-sm">{{ $severity['severity'] }}</span>
    26	                        <x-filament::badge :color="match($severity['severity_key'] ?? '') { 'critical' => 'danger', 'high' => 'danger', 'medium' => 'warning', default => 'info' }">
    27	                            {{ $severity['count'] }}
    28	                        </x-filament::badge>
    29	                    </div>
    30	                @empty
    31	                    <p class="text-sm text-gray-500">لا توجد بيانات</p>
    32	                @endforelse
    33	            </div>
    34	        </x-filament::section>
    35	    </div>
    36	
    37	    {{-- Penalty Summary & Repeat Offenders --}}
    38	    <div class="grid grid-cols-1 md:grid-cols-2 gap-4 mb-6">
    39	        <x-filament::section>
    40	            <x-slot name="heading">ملخص الجزاءات</x-slot>
    41	            <div class="space-y-3">
    42	                <div class="flex justify-between p-2 bg-gray-50 dark:bg-gray-800 rounded-lg">
    43	                    <span class="text-sm">إجمالي الجزاءات</span>
    44	                    <x-filament::badge color="primary">{{ $penaltySummary['total_penalties'] ?? 0 }}</x-filament::badge>
    45	                </div>
    46	                <div class="flex justify-between p-2 bg-gray-50 dark:bg-gray-800 rounded-lg">
    47	                    <span class="text-sm">إجمالي الغرامات</span>
    48	                    <x-filament::badge color="danger">{{ number_format($penaltySummary['total_fines'] ?? 0, 2) }} ج.م</x-filament::badge>
    49	                </div>
    50	                @foreach (($penaltySummary['by_status'] ?? []) as $status => $count)
    51	                    <div class="flex justify-between p-2 bg-gray-50 dark:bg-gray-800 rounded-lg">
    52	                        <span class="text-sm">{{ $status }}</span>
    53	                        <x-filament::badge color="info">{{ $count }}</x-filament::badge>
    54	                    </div>
    55	                @endforeach
    56	            </div>
    57	        </x-filament::section>
    58	
    59	        <x-filament::section>
    60	            <x-slot name="heading">المكررون (أكثر من مخالفة)</x-slot>
    61	            <div class="space-y-2">
    62	                @forelse ($repeatOffenders as $offender)
    63	                    <div class="flex items-center justify-between p-2 rounded-lg bg-gray-50 dark:bg-gray-800">
    64	                        <div>
    65	                            <span class="text-sm font-medium">{{ $offender['member_name'] }}</span>
    66	                            <span class="text-xs text-gray-400 mr-2">#{{ $offender['membership_number'] }}</span>
    67	                        </div>
    68	                        <x-filament::badge color="danger">{{ $offender['violation_count'] }} مخالفات</x-filament::badge>
    69	                    </div>
    70	                @empty
    71	                    <p class="text-sm text-gray-500">لا يوجد مكررون</p>
    72	                @endforelse
    73	            </div>
    74	        </x-filament::section>
    75	    </div>
    76	
    77	    {{-- Monthly Trend --}}
    78	    <x-filament::section collapsible collapsed class="mb-6">
    79	        <x-slot name="heading">اتجاه المخالفات الشهري</x-slot>
    80	        <div class="overflow-x-auto">
    81	            <table class="w-full text-sm">
    82	                <thead>
    83	                    <tr class="border-b dark:border-gray-700">
    84	                        <th class="text-right p-2">الشهر</th>
    85	                        <th class="text-center p-2">عدد المخالفات</th>
    86	                    </tr>
    87	                </thead>
    88	                <tbody>
    89	                    @foreach ($monthlyTrend as $row)
    90	                        <tr class="border-b dark:border-gray-700">
    91	                            <td class="p-2">{{ $row['month'] }}</td>
    92	                            <td class="p-2 text-center">
    93	                                <x-filament::badge :color="$row['count'] > 10 ? 'danger' : ($row['count'] > 5 ? 'warning' : 'success')">
    94	                                    {{ $row['count'] }}
    95	                                </x-filament::badge>
    96	                            </td>
    97	                        </tr>
    98	                    @endforeach
    99	                </tbody>
   100	            </table>
   101	        </div>
   102	    </x-filament::section>
   103	
   104	    {{-- Filters --}}
   105	    <x-filament::section collapsible collapsed>
   106	        <x-slot name="heading">فلاتر البحث</x-slot>
   107	        <form wire:submit="applyFilters">
   108	            {{ $this->filtersForm }}
   109	            <div class="flex gap-3 mt-4">
   110	                <x-filament::button type="submit" icon="heroicon-m-funnel">
   111	                    تطبيق الفلاتر
   112	                </x-filament::button>
   113	                <x-filament::button color="gray" wire:click="resetFilters" icon="heroicon-m-x-mark">
   114	                    إعادة تعيين
   115	                </x-filament::button>
   116	            </div>
   117	        </form>
   118	    </x-filament::section>
   119	
   120	    {{ $this->table }}
   121	</x-filament-panels::page>│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [465]: resources/views/filament/pages/reports/violations-report.blade.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [466/494]: resources/views/filament/pages/system-settings.blade.php
│ LANGUAGE: php | LINES: 126 | SIZE: 4485 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<x-filament-panels::page>
     2	    <div class="space-y-6">
     3	
     4	        {{-- General Settings --}}
     5	        <x-filament::section>
     6	            <x-slot name="heading">
     7	                <div class="flex items-center gap-2">
     8	                    <x-heroicon-o-building-office class="w-5 h-5" />
     9	                    الإعدادات العامة
    10	                </div>
    11	            </x-slot>
    12	            <x-slot name="description">
    13	                إعدادات النادي الأساسية
    14	            </x-slot>
    15	
    16	            <form wire:submit="saveGeneral">
    17	                {{ $this->generalForm }}
    18	
    19	                <div class="mt-4">
    20	                    <x-filament::button type="submit">
    21	                        حفظ الإعدادات العامة
    22	                    </x-filament::button>
    23	                </div>
    24	            </form>
    25	        </x-filament::section>
    26	
    27	        {{-- Membership Settings --}}
    28	        <x-filament::section collapsible>
    29	            <x-slot name="heading">
    30	                <div class="flex items-center gap-2">
    31	                    <x-heroicon-o-user-group class="w-5 h-5" />
    32	                    إعدادات العضوية
    33	                </div>
    34	            </x-slot>
    35	            <x-slot name="description">
    36	                إعدادات متعلقة بالعضويات والاشتراكات
    37	            </x-slot>
    38	
    39	            <form wire:submit="saveMembership">
    40	                {{ $this->membershipForm }}
    41	
    42	                <div class="mt-4">
    43	                    <x-filament::button type="submit">
    44	                        حفظ إعدادات العضوية
    45	                    </x-filament::button>
    46	                </div>
    47	            </form>
    48	        </x-filament::section>
    49	
    50	        {{-- Financial Settings --}}
    51	        <x-filament::section collapsible>
    52	            <x-slot name="heading">
    53	                <div class="flex items-center gap-2">
    54	                    <x-heroicon-o-currency-dollar class="w-5 h-5" />
    55	                    الإعدادات المالية
    56	                </div>
    57	            </x-slot>
    58	            <x-slot name="description">
    59	                إعدادات المعاملات المالية والرسوم
    60	            </x-slot>
    61	
    62	            <form wire:submit="saveFinancial">
    63	                {{ $this->financialForm }}
    64	
    65	                <div class="mt-4">
    66	                    <x-filament::button type="submit">
    67	                        حفظ الإعدادات المالية
    68	                    </x-filament::button>
    69	                </div>
    70	            </form>
    71	        </x-filament::section>
    72	
    73	        {{-- Notification Settings --}}
    74	        <x-filament::section collapsible>
    75	            <x-slot name="heading">
    76	                <div class="flex items-center gap-2">
    77	                    <x-heroicon-o-bell class="w-5 h-5" />
    78	                    إعدادات الإشعارات
    79	                </div>
    80	            </x-slot>
    81	            <x-slot name="description">
    82	                إعدادات قنوات الإشعارات المختلفة
    83	            </x-slot>
    84	
    85	            <form wire:submit="saveNotification">
    86	                {{ $this->notificationForm }}
    87	
    88	                <div class="mt-4">
    89	                    <x-filament::button type="submit">
    90	                        حفظ إعدادات الإشعارات
    91	                    </x-filament::button>
    92	                </div>
    93	            </form>
    94	        </x-filament::section>
    95	
    96	        {{-- System Settings --}}
    97	        <x-filament::section collapsible>
    98	            <x-slot name="heading">
    99	                <div class="flex items-center gap-2">
   100	                    <x-heroicon-o-cog-6-tooth class="w-5 h-5" />
   101	                    إعدادات النظام
   102	                </div>
   103	            </x-slot>
   104	            <x-slot name="description">
   105	                إعدادات فنية للنظام
   106	            </x-slot>
   107	
   108	            <form wire:submit="saveSystem">
   109	                {{ $this->systemForm }}
   110	
   111	                <div class="mt-4 flex gap-3">
   112	                    <x-filament::button type="submit">
   113	                        حفظ إعدادات النظام
   114	                    </x-filament::button>
   115	
   116	                    <x-filament::button
   117	                        color="gray"
   118	                        wire:click="clearSettingsCache"
   119	                    >
   120	                        مسح ذاكرة التخزين المؤقت
   121	                    </x-filament::button>
   122	                </div>
   123	            </form>
   124	        </x-filament::section>
   125	
   126	    </div>
   127	</x-filament-panels::page>│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [466]: resources/views/filament/pages/system-settings.blade.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [467/494]: resources/views/filament/resources/member-resource/pages/member-workflow.blade.php
│ LANGUAGE: php | LINES: 164 | SIZE: 10238 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<x-filament-panels::page>
     2	    <div class="space-y-6" dir="rtl">
     3	        {{-- Member Header --}}
     4	        <div class="bg-white dark:bg-gray-800 rounded-xl shadow-sm border border-gray-200 dark:border-gray-700 p-6">
     5	            <div class="flex items-center gap-4">
     6	                <div class="flex-shrink-0">
     7	                    @if($record->getFirstMediaUrl('member_photo'))
     8	                        <img src="{{ $record->getFirstMediaUrl('member_photo') }}" class="w-16 h-16 rounded-full object-cover ring-2 ring-primary-500">
     9	                    @else
    10	                        <div class="w-16 h-16 rounded-full bg-primary-100 dark:bg-primary-800 flex items-center justify-center">
    11	                            <span class="text-xl font-bold text-primary-600 dark:text-primary-300">{{ mb_substr($record->full_name_ar, 0, 1) }}</span>
    12	                        </div>
    13	                    @endif
    14	                </div>
    15	                <div class="flex-1">
    16	                    <h2 class="text-xl font-bold text-gray-900 dark:text-white">{{ $record->full_name_ar }}</h2>
    17	                    <div class="flex items-center gap-3 mt-1 text-sm text-gray-500 dark:text-gray-400">
    18	                        <span>{{ $record->membership_number ?? 'لم يُصدر رقم العضوية بعد' }}</span>
    19	                        <span>•</span>
    20	                        <span>{{ $record->membershipType?->name_ar }}</span>
    21	                        <span>•</span>
    22	                        <x-filament::badge :color="$record->membership_status->getColor()">
    23	                            {{ $record->membership_status->getLabel() }}
    24	                        </x-filament::badge>
    25	                    </div>
    26	                </div>
    27	                <div class="flex-shrink-0 text-left">
    28	                    <div class="text-4xl font-black text-primary-600 dark:text-primary-400">{{ $record->workflow_stage }}/8</div>
    29	                    <div class="text-xs text-gray-500 dark:text-gray-400">المرحلة الحالية</div>
    30	                </div>
    31	            </div>
    32	        </div>
    33	
    34	        {{-- Progress Bar --}}
    35	        <div class="bg-white dark:bg-gray-800 rounded-xl shadow-sm border border-gray-200 dark:border-gray-700 p-4">
    36	            <div class="flex items-center justify-between gap-1">
    37	                @foreach($this->getWorkflowStages() as $num => $stage)
    38	                    <div class="flex-1 flex flex-col items-center">
    39	                        <div @class([
    40	                            'w-10 h-10 rounded-full flex items-center justify-center text-sm font-bold transition-all duration-300',
    41	                            'bg-success-500 text-white ring-2 ring-success-300' => $stage['is_completed'],
    42	                            'bg-primary-500 text-white ring-4 ring-primary-200 animate-pulse' => $stage['is_current'],
    43	                            'bg-gray-200 dark:bg-gray-600 text-gray-500 dark:text-gray-400' => $stage['is_future'],
    44	                        ])>
    45	                            @if($stage['is_completed'])
    46	                                <x-heroicon-s-check class="w-5 h-5" />
    47	                            @else
    48	                                {{ $num }}
    49	                            @endif
    50	                        </div>
    51	                        <span @class([
    52	                            'text-xs mt-1 text-center leading-tight',
    53	                            'text-success-600 dark:text-success-400 font-semibold' => $stage['is_completed'],
    54	                            'text-primary-600 dark:text-primary-400 font-bold' => $stage['is_current'],
    55	                            'text-gray-400 dark:text-gray-500' => $stage['is_future'],
    56	                        ])>{{ $stage['title'] }}</span>
    57	                    </div>
    58	                    @if($num < 8)
    59	                        <div @class([
    60	                            'flex-1 h-1 rounded-full mx-1 mt-[-20px]',
    61	                            'bg-success-400' => $num < $record->workflow_stage,
    62	                            'bg-gray-200 dark:bg-gray-600' => $num >= $record->workflow_stage,
    63	                        ])></div>
    64	                    @endif
    65	                @endforeach
    66	            </div>
    67	        </div>
    68	
    69	        {{-- Stage Details --}}
    70	        <div class="grid grid-cols-1 md:grid-cols-2 gap-4">
    71	            @foreach($this->getWorkflowStages() as $num => $stage)
    72	                <div @class([
    73	                    'rounded-xl shadow-sm border p-5 transition-all duration-300',
    74	                    'bg-success-50 dark:bg-success-900/20 border-success-200 dark:border-success-800' => $stage['is_completed'],
    75	                    'bg-primary-50 dark:bg-primary-900/20 border-primary-300 dark:border-primary-700 ring-2 ring-primary-200 dark:ring-primary-800' => $stage['is_current'],
    76	                    'bg-white dark:bg-gray-800 border-gray-200 dark:border-gray-700 opacity-60' => $stage['is_future'],
    77	                ])>
    78	                    <div class="flex items-start gap-3">
    79	                        <div @class([
    80	                            'w-8 h-8 rounded-lg flex items-center justify-center flex-shrink-0',
    81	                            'bg-success-100 dark:bg-success-800 text-success-600 dark:text-success-300' => $stage['is_completed'],
    82	                            'bg-primary-100 dark:bg-primary-800 text-primary-600 dark:text-primary-300' => $stage['is_current'],
    83	                            'bg-gray-100 dark:bg-gray-700 text-gray-400 dark:text-gray-500' => $stage['is_future'],
    84	                        ])>
    85	                            <x-dynamic-component :component="$stage['icon']" class="w-5 h-5" />
    86	                        </div>
    87	                        <div class="flex-1">
    88	                            <div class="flex items-center justify-between">
    89	                                <h3 @class([
    90	                                    'font-bold text-sm',
    91	                                    'text-success-700 dark:text-success-300' => $stage['is_completed'],
    92	                                    'text-primary-700 dark:text-primary-300' => $stage['is_current'],
    93	                                    'text-gray-500 dark:text-gray-400' => $stage['is_future'],
    94	                                ])>
    95	                                    المرحلة {{ $num }}: {{ $stage['title'] }}
    96	                                </h3>
    97	                                @if($stage['is_completed'])
    98	                                    <x-filament::badge color="success" size="sm">مكتمل</x-filament::badge>
    99	                                @elseif($stage['is_current'])
   100	                                    <x-filament::badge color="primary" size="sm">جاري</x-filament::badge>
   101	                                @else
   102	                                    <x-filament::badge color="gray" size="sm">قادم</x-filament::badge>
   103	                                @endif
   104	                            </div>
   105	                            <p class="text-xs text-gray-500 dark:text-gray-400 mt-1">{{ $stage['description'] }}</p>
   106	
   107	                            @if($stage['completed_at'])
   108	                                <div class="mt-2 text-xs text-gray-500 dark:text-gray-400">
   109	                                    <span>✅ اكتمل في: {{ \Carbon\Carbon::parse($stage['completed_at'])->format('Y-m-d H:i') }}</span>
   110	                                    @if($stage['completed_by'])
   111	                                        <span class="mx-1">•</span>
   112	                                        <span>بواسطة: {{ $stage['completed_by'] }}</span>
   113	                                    @endif
   114	                                </div>
   115	                            @endif
   116	
   117	                            @if($stage['notes'])
   118	                                <div class="mt-2 text-xs text-gray-600 dark:text-gray-300 bg-gray-50 dark:bg-gray-700 rounded p-2">
   119	                                    {{ $stage['notes'] }}
   120	                                </div>
   121	                            @endif
   122	                        </div>
   123	                    </div>
   124	                </div>
   125	            @endforeach
   126	        </div>
   127	
   128	        {{-- Action Button --}}
   129	        @if($record->workflow_stage < 8 && $record->membership_status === \App\Enums\MembershipStatus::PENDING)
   130	            <div class="flex justify-center">
   131	                <x-filament::button
   132	                    wire:click="advanceToNextStage"
   133	                    color="success"
   134	                    size="lg"
   135	                    icon="heroicon-o-arrow-left-circle"
   136	                >
   137	                    تقديم إلى المرحلة {{ $record->workflow_stage + 1 }}
   138	                </x-filament::button>
   139	            </div>
   140	        @endif
   141	
   142	        {{-- 15-day warning for stage 7 --}}
   143	        @if($record->workflow_stage === 7 && $record->approval_date)
   144	            @php
   145	                $deadline = \Carbon\Carbon::parse($record->approval_date)->addDays(15);
   146	                $remaining = now()->diffInDays($deadline, false);
   147	            @endphp
   148	            <div @class([
   149	                'rounded-xl p-4 border text-center',
   150	                'bg-danger-50 dark:bg-danger-900/20 border-danger-300 dark:border-danger-700' => $remaining <= 3,
   151	                'bg-warning-50 dark:bg-warning-900/20 border-warning-300 dark:border-warning-700' => $remaining > 3 && $remaining <= 10,
   152	                'bg-success-50 dark:bg-success-900/20 border-success-300 dark:border-success-700' => $remaining > 10,
   153	            ])>
   154	                @if($remaining < 0)
   155	                    <p class="text-lg font-bold text-danger-600 dark:text-danger-400">⛔ انتهت مهلة السداد منذ {{ abs($remaining) }} يوم</p>
   156	                    <p class="text-sm text-danger-500 mt-1">يجب إلغاء الطلب — العضو يحتاج لتقديم استمارة جديدة</p>
   157	                @else
   158	                    <p class="text-lg font-bold {{ $remaining <= 3 ? 'text-danger-600 dark:text-danger-400' : ($remaining <= 10 ? 'text-warning-600 dark:text-warning-400' : 'text-success-600 dark:text-success-400') }}">
   159	                        ⏳ متبقي {{ $remaining }} يوم للسداد — الموعد النهائي: {{ $deadline->format('Y-m-d') }}
   160	                    </p>
   161	                @endif
   162	            </div>
   163	        @endif
   164	    </div>
   165	</x-filament-panels::page>│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [467]: resources/views/filament/resources/member-resource/pages/member-workflow.blade.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [468/494]: resources/views/filament/widgets/recent-activity-widget.blade.php
│ LANGUAGE: php | LINES: 11 | SIZE: 353 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<x-filament-widgets::widget>
     2	    <x-filament::section>
     3	        <x-slot name="heading">
     4	            <div class="flex items-center gap-2">
     5	                <x-heroicon-o-clock class="w-5 h-5" />
     6	                آخر النشاطات
     7	            </div>
     8	        </x-slot>
     9	
    10	        {{ $this->table }}
    11	    </x-filament::section>
    12	</x-filament-widgets::widget>│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [468]: resources/views/filament/widgets/recent-activity-widget.blade.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [469/494]: resources/views/pdf/receipt.blade.php
│ LANGUAGE: php | LINES: 269 | SIZE: 9021 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<!DOCTYPE html>
     2	<html dir="rtl" lang="ar">
     3	<head>
     4	    <meta charset="UTF-8">
     5	    <title>إيصال رقم {{ $receipt->receipt_number }}</title>
     6	    <style>
     7	        @font-face {
     8	            font-family: 'Cairo';
     9	            src: url('{{ storage_path("fonts/Cairo-Regular.ttf") }}') format('truetype');
    10	            font-weight: normal;
    11	        }
    12	        @font-face {
    13	            font-family: 'Cairo';
    14	            src: url('{{ storage_path("fonts/Cairo-Bold.ttf") }}') format('truetype');
    15	            font-weight: bold;
    16	        }
    17	        * {
    18	            font-family: 'Cairo', 'DejaVu Sans', sans-serif;
    19	            margin: 0;
    20	            padding: 0;
    21	            box-sizing: border-box;
    22	        }
    23	        body {
    24	            direction: rtl;
    25	            padding: 20px;
    26	            font-size: 12px;
    27	            color: #333;
    28	        }
    29	        .header {
    30	            text-align: center;
    31	            border-bottom: 2px solid #1a56db;
    32	            padding-bottom: 15px;
    33	            margin-bottom: 20px;
    34	        }
    35	        .header h1 {
    36	            font-size: 20px;
    37	            color: #1a56db;
    38	            margin-bottom: 5px;
    39	        }
    40	        .header p {
    41	            font-size: 10px;
    42	            color: #666;
    43	        }
    44	        .receipt-info {
    45	            display: flex;
    46	            justify-content: space-between;
    47	            margin-bottom: 15px;
    48	        }
    49	        .receipt-info table {
    50	            width: 48%;
    51	        }
    52	        .receipt-info td {
    53	            padding: 3px 5px;
    54	        }
    55	        .receipt-info td:first-child {
    56	            font-weight: bold;
    57	            color: #555;
    58	            width: 120px;
    59	        }
    60	        .receipt-number {
    61	            background: #1a56db;
    62	            color: white;
    63	            padding: 8px 20px;
    64	            border-radius: 5px;
    65	            display: inline-block;
    66	            font-size: 16px;
    67	            font-weight: bold;
    68	            margin-bottom: 15px;
    69	        }
    70	        .items-table {
    71	            width: 100%;
    72	            border-collapse: collapse;
    73	            margin: 15px 0;
    74	        }
    75	        .items-table th {
    76	            background: #f3f4f6;
    77	            padding: 8px;
    78	            border: 1px solid #d1d5db;
    79	            font-size: 11px;
    80	            text-align: right;
    81	        }
    82	        .items-table td {
    83	            padding: 6px 8px;
    84	            border: 1px solid #d1d5db;
    85	            font-size: 11px;
    86	        }
    87	        .totals-table {
    88	            width: 50%;
    89	            margin-right: auto;
    90	            margin-top: 10px;
    91	        }
    92	        .totals-table td {
    93	            padding: 5px 10px;
    94	            border: 1px solid #d1d5db;
    95	        }
    96	        .totals-table tr:last-child {
    97	            background: #1a56db;
    98	            color: white;
    99	            font-weight: bold;
   100	            font-size: 14px;
   101	        }
   102	        .footer {
   103	            margin-top: 30px;
   104	            text-align: center;
   105	            font-size: 10px;
   106	            color: #999;
   107	            border-top: 1px solid #e5e7eb;
   108	            padding-top: 10px;
   109	        }
   110	        .signature-area {
   111	            display: flex;
   112	            justify-content: space-between;
   113	            margin-top: 40px;
   114	        }
   115	        .signature-box {
   116	            width: 45%;
   117	            text-align: center;
   118	            border-top: 1px solid #333;
   119	            padding-top: 5px;
   120	            font-size: 11px;
   121	        }
   122	        .voided-stamp {
   123	            position: absolute;
   124	            top: 50%;
   125	            left: 50%;
   126	            transform: translate(-50%, -50%) rotate(-30deg);
   127	            font-size: 60px;
   128	            color: rgba(239, 68, 68, 0.3);
   129	            font-weight: bold;
   130	            z-index: 100;
   131	            pointer-events: none;
   132	        }
   133	        .status-badge {
   134	            display: inline-block;
   135	            padding: 3px 12px;
   136	            border-radius: 12px;
   137	            font-size: 11px;
   138	            font-weight: bold;
   139	        }
   140	        .status-paid { background: #dcfce7; color: #166534; }
   141	        .status-partial { background: #fef9c3; color: #854d0e; }
   142	        .status-voided { background: #fee2e2; color: #991b1b; }
   143	    </style>
   144	</head>
   145	<body>
   146	    @if($receipt->isVoided())
   147	        <div class="voided-stamp">ملغي</div>
   148	    @endif
   149	
   150	    <div class="header">
   151	        <h1>{{ $club_name_ar }}</h1>
   152	        <p>{{ $club_name_en }}</p>
   153	        <p>{{ $club_address }} | {{ $club_phone }}</p>
   154	    </div>
   155	
   156	    <div style="text-align: center;">
   157	        <div class="receipt-number">إيصال رقم: {{ $receipt->receipt_number }}</div>
   158	        <br>
   159	        <span class="status-badge status-{{ $receipt->status }}">
   160	            @switch($receipt->status)
   161	                @case('paid') مدفوع @break
   162	                @case('partial') مدفوع جزئياً @break
   163	                @case('voided') ملغي @break
   164	                @default {{ $receipt->status }}
   165	            @endswitch
   166	        </span>
   167	    </div>
   168	
   169	    <table style="width: 100%; margin: 15px 0;">
   170	        <tr>
   171	            <td style="width: 50%; vertical-align: top;">
   172	                <table>
   173	                    <tr><td><strong>رقم العضوية:</strong></td><td>{{ $member->membership_number }}</td></tr>
   174	                    <tr><td><strong>الاسم:</strong></td><td>{{ $member->full_name_ar }}</td></tr>
   175	                    <tr><td><strong>الهاتف:</strong></td><td>{{ $member->phone_primary }}</td></tr>
   176	                </table>
   177	            </td>
   178	            <td style="width: 50%; vertical-align: top;">
   179	                <table>
   180	                    <tr><td><strong>التاريخ:</strong></td><td>{{ $receipt->receipt_date->format('Y-m-d') }}</td></tr>
   181	                    <tr><td><strong>طريقة الدفع:</strong></td><td>
   182	                        @switch($receipt->payment_method)
   183	                            @case('cash') نقدي @break
   184	                            @case('bank_transfer') تحويل بنكي @break
   185	                            @case('check') شيك @break
   186	                            @case('credit_card') بطاقة ائتمان @break
   187	                            @default {{ $receipt->payment_method }}
   188	                        @endswitch
   189	                    </td></tr>
   190	                    @if($receipt->payment_reference)
   191	                    <tr><td><strong>المرجع:</strong></td><td>{{ $receipt->payment_reference }}</td></tr>
   192	                    @endif
   193	                </table>
   194	            </td>
   195	        </tr>
   196	    </table>
   197	
   198	    <table class="items-table">
   199	        <thead>
   200	            <tr>
   201	                <th style="width: 5%;">#</th>
   202	                <th style="width: 55%;">البيان</th>
   203	                <th style="width: 15%;">المبلغ</th>
   204	                <th style="width: 10%;">العدد</th>
   205	                <th style="width: 15%;">الإجمالي</th>
   206	            </tr>
   207	        </thead>
   208	        <tbody>
   209	            @foreach($items as $index => $item)
   210	            <tr>
   211	                <td>{{ $index + 1 }}</td>
   212	                <td>{{ $item->description_ar }}</td>
   213	                <td>{{ number_format($item->amount, 2) }}</td>
   214	                <td>{{ $item->quantity }}</td>
   215	                <td>{{ number_format($item->subtotal, 2) }}</td>
   216	            </tr>
   217	            @endforeach
   218	        </tbody>
   219	    </table>
   220	
   221	    <table class="totals-table">
   222	        <tr>
   223	            <td>الإجمالي الفرعي</td>
   224	            <td>{{ number_format($receipt->subtotal, 2) }} جنيه</td>
   225	        </tr>
   226	        @if($receipt->discount_amount > 0)
   227	        <tr>
   228	            <td>الخصم {{ $receipt->discount_reason ? "({$receipt->discount_reason})" : '' }}</td>
   229	            <td style="color: #dc2626;">- {{ number_format($receipt->discount_amount, 2) }} جنيه</td>
   230	        </tr>
   231	        @endif
   232	        @if($receipt->vat_amount > 0)
   233	        <tr>
   234	            <td>ضريبة القيمة المضافة ({{ $receipt->vat_rate }}%)</td>
   235	            <td>{{ number_format($receipt->vat_amount, 2) }} جنيه</td>
   236	        </tr>
   237	        @endif
   238	        <tr>
   239	            <td>الإجمالي</td>
   240	            <td>{{ number_format($receipt->total_amount, 2) }} جنيه</td>
   241	        </tr>
   242	        @if($receipt->amount_paid != $receipt->total_amount)
   243	        <tr>
   244	            <td>المبلغ المدفوع</td>
   245	            <td>{{ number_format($receipt->amount_paid, 2) }} جنيه</td>
   246	        </tr>
   247	        <tr>
   248	            <td>المتبقي</td>
   249	            <td style="color: #dc2626;">{{ number_format($receipt->amount_remaining, 2) }} جنيه</td>
   250	        </tr>
   251	        @endif
   252	    </table>
   253	
   254	    @if($receipt->notes_ar)
   255	    <div style="margin-top: 15px; padding: 8px; background: #f9fafb; border: 1px solid #e5e7eb; border-radius: 4px;">
   256	        <strong>ملاحظات:</strong> {{ $receipt->notes_ar }}
   257	    </div>
   258	    @endif
   259	
   260	    <div class="signature-area">
   261	        <div class="signature-box">المستلم</div>
   262	        <div class="signature-box">أمين الصندوق: {{ $cashier_name }}</div>
   263	    </div>
   264	
   265	    <div class="footer">
   266	        <p>تم الطباعة في: {{ $print_date }}</p>
   267	        <p>هذا الإيصال صادر آلياً من نظام إدارة النادي</p>
   268	    </div>
   269	</body>
   270	</html>│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [469]: resources/views/pdf/receipt.blade.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [470/494]: resources/views/prints/member-profile.blade.php
│ LANGUAGE: php | LINES: 282 | SIZE: 9927 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<!DOCTYPE html>
     2	<html lang="ar" dir="rtl">
     3	<head>
     4	    <meta charset="UTF-8">
     5	    <style>
     6	        @import url('https://fonts.googleapis.com/css2?family=Cairo:wght@300;400;600;700&display=swap');
     7	        
     8	        * {
     9	            margin: 0;
    10	            padding: 0;
    11	            box-sizing: border-box;
    12	        }
    13	        
    14	        body {
    15	            font-family: 'Cairo', 'DejaVu Sans', sans-serif;
    16	            direction: rtl;
    17	            font-size: 11px;
    18	            color: #1a1a1a;
    19	            line-height: 1.6;
    20	        }
    21	        
    22	        .header {
    23	            text-align: center;
    24	            border-bottom: 3px solid #0D8ABC;
    25	            padding-bottom: 15px;
    26	            margin-bottom: 20px;
    27	        }
    28	        
    29	        .header h1 {
    30	            font-size: 22px;
    31	            color: #0D8ABC;
    32	            margin-bottom: 5px;
    33	        }
    34	        
    35	        .header h2 {
    36	            font-size: 14px;
    37	            color: #666;
    38	        }
    39	        
    40	        .member-number {
    41	            display: inline-block;
    42	            background: #0D8ABC;
    43	            color: white;
    44	            padding: 4px 20px;
    45	            border-radius: 20px;
    46	            font-size: 16px;
    47	            font-weight: 700;
    48	            margin: 10px 0;
    49	        }
    50	        
    51	        .section {
    52	            margin-bottom: 15px;
    53	            border: 1px solid #e0e0e0;
    54	            border-radius: 8px;
    55	            overflow: hidden;
    56	        }
    57	        
    58	        .section-title {
    59	            background: #f0f9ff;
    60	            padding: 8px 15px;
    61	            font-weight: 700;
    62	            font-size: 13px;
    63	            color: #0D8ABC;
    64	            border-bottom: 1px solid #e0e0e0;
    65	        }
    66	        
    67	        .section-body {
    68	            padding: 10px 15px;
    69	        }
    70	        
    71	        .info-grid {
    72	            display: table;
    73	            width: 100%;
    74	        }
    75	        
    76	        .info-row {
    77	            display: table-row;
    78	        }
    79	        
    80	        .info-label {
    81	            display: table-cell;
    82	            padding: 4px 10px;
    83	            font-weight: 600;
    84	            color: #555;
    85	            width: 30%;
    86	            border-bottom: 1px solid #f0f0f0;
    87	        }
    88	        
    89	        .info-value {
    90	            display: table-cell;
    91	            padding: 4px 10px;
    92	            border-bottom: 1px solid #f0f0f0;
    93	        }
    94	        
    95	        .badge {
    96	            display: inline-block;
    97	            padding: 2px 10px;
    98	            border-radius: 12px;
    99	            font-size: 10px;
   100	            font-weight: 600;
   101	        }
   102	        
   103	        .badge-success { background: #d1fae5; color: #065f46; }
   104	        .badge-warning { background: #fef3c7; color: #92400e; }
   105	        .badge-danger { background: #fee2e2; color: #991b1b; }
   106	        .badge-info { background: #dbeafe; color: #1e40af; }
   107	        
   108	        table.data-table {
   109	            width: 100%;
   110	            border-collapse: collapse;
   111	            font-size: 10px;
   112	        }
   113	        
   114	        table.data-table th {
   115	            background: #f8fafc;
   116	            padding: 6px 8px;
   117	            border: 1px solid #e0e0e0;
   118	            font-weight: 700;
   119	            text-align: right;
   120	        }
   121	        
   122	        table.data-table td {
   123	            padding: 5px 8px;
   124	            border: 1px solid #e0e0e0;
   125	        }
   126	        
   127	        .footer {
   128	            margin-top: 30px;
   129	            text-align: center;
   130	            font-size: 9px;
   131	            color: #999;
   132	            border-top: 1px solid #e0e0e0;
   133	            padding-top: 10px;
   134	        }
   135	        
   136	        .photo-container {
   137	            float: left;
   138	            margin: 0 0 10px 15px;
   139	        }
   140	        
   141	        .photo-container img {
   142	            width: 90px;
   143	            height: 120px;
   144	            object-fit: cover;
   145	            border: 2px solid #0D8ABC;
   146	            border-radius: 8px;
   147	        }
   148	    </style>
   149	</head>
   150	<body>
   151	    <div class="header">
   152	        <h1>النادي — ملف العضو</h1>
   153	        <h2>بيانات العضوية الكاملة</h2>
   154	        @if($member->membership_number)
   155	            <div class="member-number">{{ $member->membership_number }}</div>
   156	        @endif
   157	    </div>
   158	
   159	    @if($member->getFirstMediaUrl('member_photo'))
   160	        <div class="photo-container">
   161	            <img src="{{ $member->getFirstMediaUrl('member_photo') }}" alt="صورة العضو">
   162	        </div>
   163	    @endif
   164	
   165	    <div class="section">
   166	        <div class="section-title">البيانات الشخصية</div>
   167	        <div class="section-body">
   168	            <div class="info-grid">
   169	                <div class="info-row">
   170	                    <div class="info-label">الاسم (عربي)</div>
   171	                    <div class="info-value">{{ $member->full_name_ar }}</div>
   172	                </div>
   173	                <div class="info-row">
   174	                    <div class="info-label">الاسم (إنجليزي)</div>
   175	                    <div class="info-value">{{ $member->full_name_en ?? '—' }}</div>
   176	                </div>
   177	                <div class="info-row">
   178	                    <div class="info-label">الرقم القومي</div>
   179	                    <div class="info-value">{{ $member->national_id }}</div>
   180	                </div>
   181	                <div class="info-row">
   182	                    <div class="info-label">النوع</div>
   183	                    <div class="info-value">{{ $member->gender?->getLabel() ?? '—' }}</div>
   184	                </div>
   185	                <div class="info-row">
   186	                    <div class="info-label">تاريخ الميلاد</div>
   187	                    <div class="info-value">{{ $member->date_of_birth?->format('Y-m-d') ?? '—' }}</div>
   188	                </div>
   189	                <div class="info-row">
   190	                    <div class="info-label">فصيلة الدم</div>
   191	                    <div class="info-value">{{ $member->blood_type ?? '—' }}</div>
   192	                </div>
   193	                <div class="info-row">
   194	                    <div class="info-label">الجوال</div>
   195	                    <div class="info-value">{{ $member->mobile }}</div>
   196	                </div>
   197	                <div class="info-row">
   198	                    <div class="info-label">البريد</div>
   199	                    <div class="info-value">{{ $member->email ?? '—' }}</div>
   200	                </div>
   201	                <div class="info-row">
   202	                    <div class="info-label">العنوان</div>
   203	                    <div class="info-value">{{ $member->address ?? '—' }}</div>
   204	                </div>
   205	            </div>
   206	        </div>
   207	    </div>
   208	
   209	    <div class="section">
   210	        <div class="section-title">بيانات العضوية</div>
   211	        <div class="section-body">
   212	            <div class="info-grid">
   213	                <div class="info-row">
   214	                    <div class="info-label">نوع العضوية</div>
   215	                    <div class="info-value">{{ $member->membershipType?->name_ar ?? '—' }}</div>
   216	                </div>
   217	                <div class="info-row">
   218	                    <div class="info-label">حالة العضوية</div>
   219	                    <div class="info-value">
   220	                        <span class="badge badge-{{ $member->membership_status === \App\Enums\MembershipStatus::ACTIVE ? 'success' : 'warning' }}">
   221	                            {{ $member->membership_status?->getLabel() }}
   222	                        </span>
   223	                    </div>
   224	                </div>
   225	                <div class="info-row">
   226	                    <div class="info-label">تاريخ التقديم</div>
   227	                    <div class="info-value">{{ $member->application_date?->format('Y-m-d') ?? '—' }}</div>
   228	                </div>
   229	                <div class="info-row">
   230	                    <div class="info-label">تاريخ الموافقة</div>
   231	                    <div class="info-value">{{ $member->approval_date?->format('Y-m-d') ?? '—' }}</div>
   232	                </div>
   233	                <div class="info-row">
   234	                    <div class="info-label">تاريخ السداد</div>
   235	                    <div class="info-value">{{ $member->payment_date?->format('Y-m-d') ?? '—' }}</div>
   236	                </div>
   237	                <div class="info-row">
   238	                    <div class="info-label">المُعرّف الأول</div>
   239	                    <div class="info-value">{{ $member->referee1?->full_name_ar ?? '—' }}</div>
   240	                </div>
   241	                <div class="info-row">
   242	                    <div class="info-label">المُعرّف الثاني</div>
   243	                    <div class="info-value">{{ $member->referee2?->full_name_ar ?? '—' }}</div>
   244	                </div>
   245	            </div>
   246	        </div>
   247	    </div>
   248	
   249	    @if($member->dependents->count() > 0)
   250	    <div class="section">
   251	        <div class="section-title">التابعون ({{ $member->dependents->count() }})</div>
   252	        <div class="section-body">
   253	            <table class="data-table">
   254	                <thead>
   255	                    <tr>
   256	                        <th>الاسم</th>
   257	                        <th>صلة القرابة</th>
   258	                        <th>تاريخ الميلاد</th>
   259	                        <th>الرقم القومي</th>
   260	                        <th>الحالة</th>
   261	                    </tr>
   262	                </thead>
   263	                <tbody>
   264	                    @foreach($member->dependents as $dep)
   265	                    <tr>
   266	                        <td>{{ $dep->full_name_ar }}</td>
   267	                        <td>{{ $dep->relationship }}</td>
   268	                        <td>{{ $dep->date_of_birth?->format('Y-m-d') }}</td>
   269	                        <td>{{ $dep->national_id ?? '—' }}</td>
   270	                        <td>{{ $dep->status?->getLabel() ?? $dep->status }}</td>
   271	                    </tr>
   272	                    @endforeach
   273	                </tbody>
   274	            </table>
   275	        </div>
   276	    </div>
   277	    @endif
   278	
   279	    <div class="footer">
   280	        طُبع بتاريخ {{ now()->format('Y-m-d H:i') }} — بواسطة {{ auth()->user()?->name ?? 'النظام' }} — هذا المستند مُنشأ إلكترونياً
   281	    </div>
   282	</body>
   283	</html>│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [470]: resources/views/prints/member-profile.blade.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [471/494]: resources/views/reports/aging.blade.php
│ LANGUAGE: php | LINES: 64 | SIZE: 2981 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<!DOCTYPE html>
     2	<html lang="ar" dir="rtl">
     3	<head>
     4	    <meta charset="utf-8">
     5	    <title>تقرير أعمار الديون</title>
     6	    <style>
     7	        * { margin: 0; padding: 0; box-sizing: border-box; }
     8	        body { font-family: 'Arial', sans-serif; font-size: 11px; direction: rtl; padding: 20px; color: #333; }
     9	        .header { text-align: center; margin-bottom: 20px; border-bottom: 2px solid #ea580c; padding-bottom: 10px; }
    10	        .header h1 { font-size: 18px; color: #9a3412; }
    11	        .header p { font-size: 10px; color: #666; }
    12	        .aging-grid { display: flex; gap: 10px; margin-bottom: 15px; }
    13	        .aging-box { flex: 1; text-align: center; border: 1px solid #e5e7eb; padding: 10px; border-radius: 6px; }
    14	        .aging-box.danger { border-color: #fca5a5; background: #fef2f2; }
    15	        table { width: 100%; border-collapse: collapse; margin-top: 10px; }
    16	        th { background: #ea580c; color: #fff; padding: 6px 8px; text-align: right; font-size: 10px; }
    17	        td { padding: 5px 8px; border-bottom: 1px solid #e2e8f0; font-size: 10px; }
    18	        tr:nth-child(even) { background: #f8fafc; }
    19	        .footer { margin-top: 20px; text-align: center; font-size: 9px; color: #94a3b8; }
    20	    </style>
    21	</head>
    22	<body>
    23	    <div class="header">
    24	        <h1>تقرير أعمار الديون</h1>
    25	        <p>تاريخ الإصدار: {{ $generated_at->format('d/m/Y H:i') }}</p>
    26	    </div>
    27	
    28	    <div class="aging-grid">
    29	        @foreach ($aging_brackets as $i => $bracket)
    30	            <div class="aging-box {{ $i === count($aging_brackets) - 1 ? 'danger' : '' }}">
    31	                <div style="font-size: 9px; color: #64748b;">{{ $bracket['bracket'] }} يوم</div>
    32	                <div style="font-size: 16px; font-weight: bold;">{{ number_format($bracket['total'], 2) }}</div>
    33	                <div style="font-size: 9px; color: #94a3b8;">{{ $bracket['count'] }} إيصال</div>
    34	            </div>
    35	        @endforeach
    36	    </div>
    37	
    38	    <table>
    39	        <thead>
    40	            <tr>
    41	                <th>رقم الإيصال</th>
    42	                <th>العضو</th>
    43	                <th>التاريخ</th>
    44	                <th>المبلغ</th>
    45	                <th>الحالة</th>
    46	            </tr>
    47	        </thead>
    48	        <tbody>
    49	            @foreach ($receipts as $receipt)
    50	                <tr>
    51	                    <td>{{ $receipt->receipt_number }}</td>
    52	                    <td>{{ $receipt->member?->full_name_ar }}</td>
    53	                    <td>{{ $receipt->receipt_date?->format('d/m/Y') }}</td>
    54	                    <td style="font-weight: bold; color: #991b1b;">{{ number_format($receipt->total_amount, 2) }} ج.م</td>
    55	                    <td>{{ $receipt->status?->getLabel() }}</td>
    56	                </tr>
    57	            @endforeach
    58	        </tbody>
    59	    </table>
    60	
    61	    <div class="footer">
    62	        <p>تم إنشاء هذا التقرير تلقائياً — نظام إدارة النادي — {{ $generated_at->format('d/m/Y H:i:s') }}</p>
    63	    </div>
    64	</body>
    65	</html>│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [471]: resources/views/reports/aging.blade.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [472/494]: resources/views/reports/churn.blade.php
│ LANGUAGE: php | LINES: 70 | SIZE: 3253 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<!DOCTYPE html>
     2	<html lang="ar" dir="rtl">
     3	<head>
     4	    <meta charset="utf-8">
     5	    <title>تقرير تسرب الأعضاء</title>
     6	    <style>
     7	        * { margin: 0; padding: 0; box-sizing: border-box; }
     8	        body { font-family: 'Arial', sans-serif; font-size: 11px; direction: rtl; padding: 20px; color: #333; }
     9	        .header { text-align: center; margin-bottom: 20px; border-bottom: 2px solid #be123c; padding-bottom: 10px; }
    10	        .header h1 { font-size: 18px; color: #9f1239; }
    11	        .header p { font-size: 10px; color: #666; }
    12	        table { width: 100%; border-collapse: collapse; margin-top: 10px; }
    13	        th { background: #be123c; color: #fff; padding: 6px 8px; text-align: right; font-size: 10px; }
    14	        td { padding: 5px 8px; border-bottom: 1px solid #e2e8f0; font-size: 10px; text-align: center; }
    15	        td:first-child { text-align: right; }
    16	        tr:nth-child(even) { background: #f8fafc; }
    17	        .positive { color: #166534; font-weight: bold; }
    18	        .negative { color: #991b1b; font-weight: bold; }
    19	        tfoot td { background: #f1f5f9; font-weight: bold; }
    20	        .footer { margin-top: 20px; text-align: center; font-size: 9px; color: #94a3b8; }
    21	    </style>
    22	</head>
    23	<body>
    24	    <div class="header">
    25	        <h1>تقرير تسرب الأعضاء</h1>
    26	        <p>آخر {{ $months }} شهر | تاريخ الإصدار: {{ $generated_at->format('d/m/Y H:i') }}</p>
    27	    </div>
    28	
    29	    <table>
    30	        <thead>
    31	            <tr>
    32	                <th>الشهر</th>
    33	                <th>أعضاء جدد</th>
    34	                <th>استقالة</th>
    35	                <th>إلغاء</th>
    36	                <th>انتهاء</th>
    37	                <th>صافي التغيير</th>
    38	            </tr>
    39	        </thead>
    40	        <tbody>
    41	            @foreach ($churn_data as $row)
    42	                <tr>
    43	                    <td>{{ $row['month'] }}</td>
    44	                    <td class="positive">+{{ $row['new'] }}</td>
    45	                    <td>{{ $row['resigned'] }}</td>
    46	                    <td>{{ $row['cancelled'] }}</td>
    47	                    <td>{{ $row['expired'] }}</td>
    48	                    <td class="{{ $row['net_change'] >= 0 ? 'positive' : 'negative' }}">
    49	                        {{ $row['net_change'] >= 0 ? '+' : '' }}{{ $row['net_change'] }}
    50	                    </td>
    51	                </tr>
    52	            @endforeach
    53	        </tbody>
    54	        <tfoot>
    55	            <tr>
    56	                <td style="text-align: right;">الإجمالي</td>
    57	                <td class="positive">+{{ array_sum(array_column($churn_data, 'new')) }}</td>
    58	                <td>{{ array_sum(array_column($churn_data, 'resigned')) }}</td>
    59	                <td>{{ array_sum(array_column($churn_data, 'cancelled')) }}</td>
    60	                <td>{{ array_sum(array_column($churn_data, 'expired')) }}</td>
    61	                @php $total = array_sum(array_column($churn_data, 'net_change')); @endphp
    62	                <td class="{{ $total >= 0 ? 'positive' : 'negative' }}">{{ $total >= 0 ? '+' : '' }}{{ $total }}</td>
    63	            </tr>
    64	        </tfoot>
    65	    </table>
    66	
    67	    <div class="footer">
    68	        <p>تم إنشاء هذا التقرير تلقائياً — نظام إدارة النادي — {{ $generated_at->format('d/m/Y H:i:s') }}</p>
    69	    </div>
    70	</body>
    71	</html>│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [472]: resources/views/reports/churn.blade.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [473/494]: resources/views/reports/financial.blade.php
│ LANGUAGE: php | LINES: 102 | SIZE: 4643 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<!DOCTYPE html>
     2	<html lang="ar" dir="rtl">
     3	<head>
     4	    <meta charset="utf-8">
     5	    <title>التقرير المالي</title>
     6	    <style>
     7	        * { margin: 0; padding: 0; box-sizing: border-box; }
     8	        body { font-family: 'Arial', sans-serif; font-size: 11px; direction: rtl; padding: 20px; color: #333; }
     9	        .header { text-align: center; margin-bottom: 20px; border-bottom: 2px solid #16a34a; padding-bottom: 10px; }
    10	        .header h1 { font-size: 18px; color: #166534; margin-bottom: 5px; }
    11	        .header p { font-size: 10px; color: #666; }
    12	        .stats-grid { display: flex; gap: 10px; margin-bottom: 15px; }
    13	        .stat-box { flex: 1; background: #f0fdf4; border: 1px solid #bbf7d0; padding: 10px; border-radius: 6px; text-align: center; }
    14	        .stat-box .label { font-size: 9px; color: #64748b; }
    15	        .stat-box .value { font-size: 16px; font-weight: bold; color: #166534; }
    16	        table { width: 100%; border-collapse: collapse; margin-top: 10px; }
    17	        th { background: #16a34a; color: #fff; padding: 6px 8px; text-align: right; font-size: 10px; }
    18	        td { padding: 5px 8px; border-bottom: 1px solid #e2e8f0; font-size: 10px; }
    19	        tr:nth-child(even) { background: #f8fafc; }
    20	        .footer { margin-top: 20px; text-align: center; font-size: 9px; color: #94a3b8; border-top: 1px solid #e2e8f0; padding-top: 10px; }
    21	        .amount { font-weight: bold; }
    22	        .amount-positive { color: #166534; }
    23	        .amount-negative { color: #991b1b; }
    24	    </style>
    25	</head>
    26	<body>
    27	    <div class="header">
    28	        <h1>التقرير المالي</h1>
    29	        <p>تاريخ الإصدار: {{ $generated_at->format('d/m/Y H:i') }}</p>
    30	    </div>
    31	
    32	    @if (!empty($collection_summary))
    33	        <div class="stats-grid">
    34	            <div class="stat-box">
    35	                <div class="label">إجمالي المحصّل</div>
    36	                <div class="value amount-positive">{{ number_format($collection_summary['total_collected'], 2) }} ج.م</div>
    37	            </div>
    38	            <div class="stat-box">
    39	                <div class="label">إجمالي المستحق</div>
    40	                <div class="value amount-negative">{{ number_format($collection_summary['total_outstanding'], 2) }} ج.م</div>
    41	            </div>
    42	            <div class="stat-box">
    43	                <div class="label">نسبة التحصيل</div>
    44	                <div class="value">{{ $collection_summary['collection_rate'] }}%</div>
    45	            </div>
    46	            <div class="stat-box">
    47	                <div class="label">عدد الإيصالات</div>
    48	                <div class="value">{{ $collection_summary['receipt_count'] }}</div>
    49	            </div>
    50	        </div>
    51	    @endif
    52	
    53	    @if (!empty($revenue_by_category))
    54	        <h3 style="margin-bottom: 8px; color: #166534;">الإيرادات حسب الفئة</h3>
    55	        <table style="margin-bottom: 15px;">
    56	            <thead>
    57	                <tr>
    58	                    <th>الفئة</th>
    59	                    <th>المبلغ</th>
    60	                </tr>
    61	            </thead>
    62	            <tbody>
    63	                @foreach ($revenue_by_category as $cat)
    64	                    <tr>
    65	                        <td>{{ $cat['label'] }}</td>
    66	                        <td class="amount amount-positive">{{ number_format($cat['total'], 2) }} ج.م</td>
    67	                    </tr>
    68	                @endforeach
    69	            </tbody>
    70	        </table>
    71	    @endif
    72	
    73	    <h3 style="margin-bottom: 8px; color: #166534;">تفاصيل الإيصالات ({{ $receipts->count() }} إيصال)</h3>
    74	    <table>
    75	        <thead>
    76	            <tr>
    77	                <th>رقم الإيصال</th>
    78	                <th>العضو</th>
    79	                <th>التاريخ</th>
    80	                <th>المبلغ</th>
    81	                <th>طريقة الدفع</th>
    82	                <th>الحالة</th>
    83	            </tr>
    84	        </thead>
    85	        <tbody>
    86	            @foreach ($receipts as $receipt)
    87	                <tr>
    88	                    <td>{{ $receipt->receipt_number }}</td>
    89	                    <td>{{ $receipt->member?->full_name_ar }}</td>
    90	                    <td>{{ $receipt->receipt_date?->format('d/m/Y') }}</td>
    91	                    <td class="amount">{{ number_format($receipt->total_amount, 2) }} ج.م</td>
    92	                    <td>{{ $receipt->payment_method }}</td>
    93	                    <td>{{ $receipt->status?->getLabel() }}</td>
    94	                </tr>
    95	            @endforeach
    96	        </tbody>
    97	    </table>
    98	
    99	    <div class="footer">
   100	        <p>تم إنشاء هذا التقرير تلقائياً — نظام إدارة النادي — {{ $generated_at->format('d/m/Y H:i:s') }}</p>
   101	    </div>
   102	</body>
   103	</html>│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [473]: resources/views/reports/financial.blade.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [474/494]: resources/views/reports/installments.blade.php
│ LANGUAGE: php | LINES: 53 | SIZE: 2422 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<!DOCTYPE html>
     2	<html lang="ar" dir="rtl">
     3	<head>
     4	    <meta charset="utf-8">
     5	    <title>تقرير الأقساط</title>
     6	    <style>
     7	        * { margin: 0; padding: 0; box-sizing: border-box; }
     8	        body { font-family: 'Arial', sans-serif; font-size: 11px; direction: rtl; padding: 20px; color: #333; }
     9	        .header { text-align: center; margin-bottom: 20px; border-bottom: 2px solid #7c3aed; padding-bottom: 10px; }
    10	        .header h1 { font-size: 18px; color: #5b21b6; }
    11	        .header p { font-size: 10px; color: #666; }
    12	        table { width: 100%; border-collapse: collapse; margin-top: 10px; }
    13	        th { background: #7c3aed; color: #fff; padding: 6px 8px; text-align: right; font-size: 10px; }
    14	        td { padding: 5px 8px; border-bottom: 1px solid #e2e8f0; font-size: 10px; }
    15	        tr:nth-child(even) { background: #f8fafc; }
    16	        .footer { margin-top: 20px; text-align: center; font-size: 9px; color: #94a3b8; }
    17	    </style>
    18	</head>
    19	<body>
    20	    <div class="header">
    21	        <h1>تقرير الأقساط</h1>
    22	        <p>تاريخ الإصدار: {{ $generated_at->format('d/m/Y H:i') }}</p>
    23	    </div>
    24	
    25	    <table>
    26	        <thead>
    27	            <tr>
    28	                <th>العضو</th>
    29	                <th>المبلغ الإجمالي</th>
    30	                <th>عدد الأقساط</th>
    31	                <th>المسدد</th>
    32	                <th>المتبقي</th>
    33	                <th>الحالة</th>
    34	            </tr>
    35	        </thead>
    36	        <tbody>
    37	            @foreach ($plans as $plan)
    38	                <tr>
    39	                    <td>{{ $plan->member?->full_name_ar }} (#{{ $plan->member?->membership_number }})</td>
    40	                    <td>{{ number_format($plan->total_amount, 2) }} ج.م</td>
    41	                    <td>{{ $plan->number_of_installments }}</td>
    42	                    <td style="color: #166534;">{{ number_format($plan->installments->where('status', 'paid')->sum('amount'), 2) }} ج.م</td>
    43	                    <td style="color: #991b1b;">{{ number_format($plan->installments->where('status', 'pending')->sum('amount'), 2) }} ج.م</td>
    44	                    <td>{{ $plan->status?->getLabel() }}</td>
    45	                </tr>
    46	            @endforeach
    47	        </tbody>
    48	    </table>
    49	
    50	    <div class="footer">
    51	        <p>تم إنشاء هذا التقرير تلقائياً — نظام إدارة النادي — {{ $generated_at->format('d/m/Y H:i:s') }}</p>
    52	    </div>
    53	</body>
    54	</html>│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [474]: resources/views/reports/installments.blade.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [475/494]: resources/views/reports/membership.blade.php
│ LANGUAGE: php | LINES: 100 | SIZE: 4632 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<!DOCTYPE html>
     2	<html lang="ar" dir="rtl">
     3	<head>
     4	    <meta charset="utf-8">
     5	    <title>تقرير العضويات</title>
     6	    <style>
     7	        * { margin: 0; padding: 0; box-sizing: border-box; }
     8	        body { font-family: 'Arial', sans-serif; font-size: 11px; direction: rtl; padding: 20px; color: #333; }
     9	        .header { text-align: center; margin-bottom: 20px; border-bottom: 2px solid #2563eb; padding-bottom: 10px; }
    10	        .header h1 { font-size: 18px; color: #1e40af; margin-bottom: 5px; }
    11	        .header p { font-size: 10px; color: #666; }
    12	        .summary { display: flex; flex-wrap: wrap; gap: 10px; margin-bottom: 15px; }
    13	        .summary-item { background: #f0f9ff; border: 1px solid #bfdbfe; padding: 8px 12px; border-radius: 4px; text-align: center; flex: 1; min-width: 120px; }
    14	        .summary-item .label { font-size: 9px; color: #64748b; }
    15	        .summary-item .value { font-size: 14px; font-weight: bold; color: #1e40af; }
    16	        table { width: 100%; border-collapse: collapse; margin-top: 10px; }
    17	        th { background: #2563eb; color: #fff; padding: 6px 8px; text-align: right; font-size: 10px; }
    18	        td { padding: 5px 8px; border-bottom: 1px solid #e2e8f0; font-size: 10px; }
    19	        tr:nth-child(even) { background: #f8fafc; }
    20	        .footer { margin-top: 20px; text-align: center; font-size: 9px; color: #94a3b8; border-top: 1px solid #e2e8f0; padding-top: 10px; }
    21	        .badge { display: inline-block; padding: 2px 8px; border-radius: 12px; font-size: 9px; font-weight: bold; }
    22	        .badge-success { background: #dcfce7; color: #166534; }
    23	        .badge-warning { background: #fef9c3; color: #854d0e; }
    24	        .badge-danger { background: #fecaca; color: #991b1b; }
    25	        .badge-info { background: #dbeafe; color: #1e40af; }
    26	    </style>
    27	</head>
    28	<body>
    29	    <div class="header">
    30	        <h1>تقرير العضويات</h1>
    31	        <p>تاريخ الإصدار: {{ $generated_at->format('d/m/Y H:i') }}</p>
    32	        @if (!empty($filters))
    33	            <p>الفلاتر المطبقة:
    34	                @foreach ($filters as $key => $value)
    35	                    {{ $key }}: {{ $value }} |
    36	                @endforeach
    37	            </p>
    38	        @endif
    39	    </div>
    40	
    41	    @if (!empty($summary_by_type))
    42	        <h3 style="margin-bottom: 8px; color: #1e40af;">ملخص حسب نوع العضوية</h3>
    43	        <table style="margin-bottom: 15px;">
    44	            <thead>
    45	                <tr>
    46	                    <th>النوع</th>
    47	                    <th>الإجمالي</th>
    48	                    <th>نشط</th>
    49	                    <th>موقوف</th>
    50	                    <th>مجمد</th>
    51	                </tr>
    52	            </thead>
    53	            <tbody>
    54	                @foreach ($summary_by_type as $type)
    55	                    <tr>
    56	                        <td>{{ $type['type_name'] }}</td>
    57	                        <td>{{ $type['total'] }}</td>
    58	                        <td>{{ $type['active'] }}</td>
    59	                        <td>{{ $type['suspended'] }}</td>
    60	                        <td>{{ $type['frozen'] }}</td>
    61	                    </tr>
    62	                @endforeach
    63	            </tbody>
    64	        </table>
    65	    @endif
    66	
    67	    <h3 style="margin-bottom: 8px; color: #1e40af;">قائمة الأعضاء ({{ $members->count() }} عضو)</h3>
    68	    <table>
    69	        <thead>
    70	            <tr>
    71	                <th>رقم العضوية</th>
    72	                <th>الاسم</th>
    73	                <th>نوع العضوية</th>
    74	                <th>الحالة</th>
    75	                <th>الهاتف</th>
    76	                <th>تاريخ الانضمام</th>
    77	            </tr>
    78	        </thead>
    79	        <tbody>
    80	            @foreach ($members as $member)
    81	                <tr>
    82	                    <td>{{ $member->membership_number }}</td>
    83	                    <td>{{ $member->full_name_ar }}</td>
    84	                    <td>{{ $member->membershipType?->name_ar }}</td>
    85	                    <td>
    86	                        <span class="badge badge-{{ $member->membership_status?->getColor() === 'success' ? 'success' : ($member->membership_status?->getColor() === 'danger' ? 'danger' : 'info') }}">
    87	                            {{ $member->membership_status?->getLabel() }}
    88	                        </span>
    89	                    </td>
    90	                    <td>{{ $member->phone }}</td>
    91	                    <td>{{ $member->join_date?->format('d/m/Y') }}</td>
    92	                </tr>
    93	            @endforeach
    94	        </tbody>
    95	    </table>
    96	
    97	    <div class="footer">
    98	        <p>تم إنشاء هذا التقرير تلقائياً — نظام إدارة النادي — {{ $generated_at->format('d/m/Y H:i:s') }}</p>
    99	    </div>
   100	</body>
   101	</html>│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [475]: resources/views/reports/membership.blade.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [476/494]: resources/views/reports/subscriptions.blade.php
│ LANGUAGE: php | LINES: 80 | SIZE: 3650 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<!DOCTYPE html>
     2	<html lang="ar" dir="rtl">
     3	<head>
     4	    <meta charset="utf-8">
     5	    <title>تقرير الاشتراكات</title>
     6	    <style>
     7	        * { margin: 0; padding: 0; box-sizing: border-box; }
     8	        body { font-family: 'Arial', sans-serif; font-size: 11px; direction: rtl; padding: 20px; color: #333; }
     9	        .header { text-align: center; margin-bottom: 20px; border-bottom: 2px solid #1d4ed8; padding-bottom: 10px; }
    10	        .header h1 { font-size: 18px; color: #1e40af; margin-bottom: 5px; }
    11	        .header p { font-size: 10px; color: #666; }
    12	        table { width: 100%; border-collapse: collapse; margin-top: 10px; }
    13	        th { background: #1d4ed8; color: #fff; padding: 6px 8px; text-align: right; font-size: 10px; }
    14	        td { padding: 5px 8px; border-bottom: 1px solid #e2e8f0; font-size: 10px; }
    15	        tr:nth-child(even) { background: #f8fafc; }
    16	        .footer { margin-top: 20px; text-align: center; font-size: 9px; color: #94a3b8; border-top: 1px solid #e2e8f0; padding-top: 10px; }
    17	        .badge { display: inline-block; padding: 2px 8px; border-radius: 12px; font-size: 9px; font-weight: bold; }
    18	        .badge-active { background: #dcfce7; color: #166534; }
    19	        .badge-expired { background: #fecaca; color: #991b1b; }
    20	        .badge-cancelled { background: #e5e7eb; color: #374151; }
    21	    </style>
    22	</head>
    23	<body>
    24	    <div class="header">
    25	        <h1>تقرير الاشتراكات</h1>
    26	        <p>تاريخ الإصدار: {{ $generated_at->format('d/m/Y H:i') }}</p>
    27	    </div>
    28	
    29	    @if (!empty($summary_by_plan))
    30	        <h3 style="margin-bottom: 8px; color: #1e40af;">ملخص حسب الخطة</h3>
    31	        <table style="margin-bottom: 15px;">
    32	            <thead>
    33	                <tr><th>الخطة</th><th>الإجمالي</th><th>نشط</th><th>منتهي</th><th>ملغي</th></tr>
    34	            </thead>
    35	            <tbody>
    36	                @foreach ($summary_by_plan as $plan)
    37	                    <tr>
    38	                        <td>{{ $plan['plan_name'] }}</td>
    39	                        <td>{{ $plan['total'] }}</td>
    40	                        <td>{{ $plan['active'] }}</td>
    41	                        <td>{{ $plan['expired'] }}</td>
    42	                        <td>{{ $plan['cancelled'] }}</td>
    43	                    </tr>
    44	                @endforeach
    45	            </tbody>
    46	        </table>
    47	    @endif
    48	
    49	    <h3 style="margin-bottom: 8px; color: #1e40af;">تفاصيل الاشتراكات ({{ $subscriptions->count() }})</h3>
    50	    <table>
    51	        <thead>
    52	            <tr>
    53	                <th>رقم العضوية</th>
    54	                <th>العضو</th>
    55	                <th>الخطة</th>
    56	                <th>البداية</th>
    57	                <th>النهاية</th>
    58	                <th>الحالة</th>
    59	                <th>المبلغ</th>
    60	            </tr>
    61	        </thead>
    62	        <tbody>
    63	            @foreach ($subscriptions as $sub)
    64	                <tr>
    65	                    <td>{{ $sub->member?->membership_number }}</td>
    66	                    <td>{{ $sub->member?->full_name_ar }}</td>
    67	                    <td>{{ $sub->subscriptionPlan?->name_ar }}</td>
    68	                    <td>{{ $sub->start_date?->format('d/m/Y') }}</td>
    69	                    <td>{{ $sub->end_date?->format('d/m/Y') }}</td>
    70	                    <td>{{ $sub->status?->getLabel() }}</td>
    71	                    <td>{{ number_format($sub->amount ?? 0, 2) }} ج.م</td>
    72	                </tr>
    73	            @endforeach
    74	        </tbody>
    75	    </table>
    76	
    77	    <div class="footer">
    78	        <p>تم إنشاء هذا التقرير تلقائياً — نظام إدارة النادي — {{ $generated_at->format('d/m/Y H:i:s') }}</p>
    79	    </div>
    80	</body>
    81	</html>│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [476]: resources/views/reports/subscriptions.blade.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [477/494]: resources/views/reports/top-paying-members.blade.php
│ LANGUAGE: php | LINES: 54 | SIZE: 2405 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<!DOCTYPE html>
     2	<html lang="ar" dir="rtl">
     3	<head>
     4	    <meta charset="utf-8">
     5	    <title>أعلى الأعضاء سداداً</title>
     6	    <style>
     7	        * { margin: 0; padding: 0; box-sizing: border-box; }
     8	        body { font-family: 'Arial', sans-serif; font-size: 11px; direction: rtl; padding: 20px; color: #333; }
     9	        .header { text-align: center; margin-bottom: 20px; border-bottom: 2px solid #d97706; padding-bottom: 10px; }
    10	        .header h1 { font-size: 18px; color: #92400e; }
    11	        .header p { font-size: 10px; color: #666; }
    12	        table { width: 100%; border-collapse: collapse; margin-top: 10px; }
    13	        th { background: #d97706; color: #fff; padding: 6px 8px; text-align: right; font-size: 10px; }
    14	        td { padding: 5px 8px; border-bottom: 1px solid #e2e8f0; font-size: 10px; }
    15	        tr:nth-child(even) { background: #f8fafc; }
    16	        .rank-1 td { background: #fef3c7; }
    17	        .rank-2 td { background: #f3f4f6; }
    18	        .rank-3 td { background: #fed7aa; }
    19	        .footer { margin-top: 20px; text-align: center; font-size: 9px; color: #94a3b8; }
    20	    </style>
    21	</head>
    22	<body>
    23	    <div class="header">
    24	        <h1>أعلى الأعضاء سداداً</h1>
    25	        <p>الفترة: {{ $date_from }} إلى {{ $date_to }} | تاريخ الإصدار: {{ $generated_at->format('d/m/Y H:i') }}</p>
    26	    </div>
    27	
    28	    <table>
    29	        <thead>
    30	            <tr>
    31	                <th>#</th>
    32	                <th>رقم العضوية</th>
    33	                <th>اسم العضو</th>
    34	                <th>عدد الإيصالات</th>
    35	                <th>إجمالي المدفوعات</th>
    36	            </tr>
    37	        </thead>
    38	        <tbody>
    39	            @foreach ($members as $index => $member)
    40	                <tr class="{{ $index < 3 ? 'rank-' . ($index + 1) : '' }}">
    41	                    <td>{{ $index + 1 }}</td>
    42	                    <td>{{ $member['membership_number'] }}</td>
    43	                    <td>{{ $member['member_name'] }}</td>
    44	                    <td>{{ $member['receipt_count'] }}</td>
    45	                    <td style="font-weight: bold;">{{ number_format($member['total_paid'], 2) }} ج.م</td>
    46	                </tr>
    47	            @endforeach
    48	        </tbody>
    49	    </table>
    50	
    51	    <div class="footer">
    52	        <p>تم إنشاء هذا التقرير تلقائياً — نظام إدارة النادي — {{ $generated_at->format('d/m/Y H:i:s') }}</p>
    53	    </div>
    54	</body>
    55	</html>│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [477]: resources/views/reports/top-paying-members.blade.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [478/494]: resources/views/reports/violations.blade.php
│ LANGUAGE: php | LINES: 78 | SIZE: 3608 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<!DOCTYPE html>
     2	<html lang="ar" dir="rtl">
     3	<head>
     4	    <meta charset="utf-8">
     5	    <title>تقرير المخالفات</title>
     6	    <style>
     7	        * { margin: 0; padding: 0; box-sizing: border-box; }
     8	        body { font-family: 'Arial', sans-serif; font-size: 11px; direction: rtl; padding: 20px; color: #333; }
     9	        .header { text-align: center; margin-bottom: 20px; border-bottom: 2px solid #dc2626; padding-bottom: 10px; }
    10	        .header h1 { font-size: 18px; color: #991b1b; margin-bottom: 5px; }
    11	        .header p { font-size: 10px; color: #666; }
    12	        table { width: 100%; border-collapse: collapse; margin-top: 10px; }
    13	        th { background: #dc2626; color: #fff; padding: 6px 8px; text-align: right; font-size: 10px; }
    14	        td { padding: 5px 8px; border-bottom: 1px solid #e2e8f0; font-size: 10px; }
    15	        tr:nth-child(even) { background: #f8fafc; }
    16	        .footer { margin-top: 20px; text-align: center; font-size: 9px; color: #94a3b8; border-top: 1px solid #e2e8f0; padding-top: 10px; }
    17	        .summary-section { margin-bottom: 15px; }
    18	        .summary-section h3 { color: #991b1b; margin-bottom: 8px; }
    19	        .badge { display: inline-block; padding: 2px 8px; border-radius: 12px; font-size: 9px; font-weight: bold; }
    20	        .badge-critical { background: #fecaca; color: #991b1b; }
    21	        .badge-high { background: #fed7aa; color: #9a3412; }
    22	        .badge-medium { background: #fef9c3; color: #854d0e; }
    23	        .badge-low { background: #dbeafe; color: #1e40af; }
    24	    </style>
    25	</head>
    26	<body>
    27	    <div class="header">
    28	        <h1>تقرير المخالفات والجزاءات</h1>
    29	        <p>تاريخ الإصدار: {{ $generated_at->format('d/m/Y H:i') }}</p>
    30	    </div>
    31	
    32	    <div class="summary-section">
    33	        <h3>المخالفات حسب الخطورة</h3>
    34	        <table style="margin-bottom: 10px;">
    35	            <thead><tr><th>الخطورة</th><th>العدد</th></tr></thead>
    36	            <tbody>
    37	                @foreach ($by_severity as $s)
    38	                    <tr><td>{{ $s['severity'] }}</td><td>{{ $s['count'] }}</td></tr>
    39	                @endforeach
    40	            </tbody>
    41	        </table>
    42	    </div>
    43	
    44	    <div class="summary-section">
    45	        <h3>ملخص الجزاءات</h3>
    46	        <p>إجمالي الجزاءات: {{ $penalty_summary['total_penalties'] ?? 0 }} | إجمالي الغرامات: {{ number_format($penalty_summary['total_fines'] ?? 0, 2) }} ج.م</p>
    47	    </div>
    48	
    49	    <h3 style="margin-bottom: 8px; color: #991b1b;">تفاصيل المخالفات ({{ $violations->count() }})</h3>
    50	    <table>
    51	        <thead>
    52	            <tr>
    53	                <th>#</th>
    54	                <th>العضو</th>
    55	                <th>النوع</th>
    56	                <th>الخطورة</th>
    57	                <th>الحالة</th>
    58	                <th>التاريخ</th>
    59	            </tr>
    60	        </thead>
    61	        <tbody>
    62	            @foreach ($violations as $v)
    63	                <tr>
    64	                    <td>{{ $v->id }}</td>
    65	                    <td>{{ $v->member?->full_name_ar }} (#{{ $v->member?->membership_number }})</td>
    66	                    <td>{{ $v->violationType?->name_ar }}</td>
    67	                    <td>{{ $v->severity?->getLabel() }}</td>
    68	                    <td>{{ $v->status?->getLabel() }}</td>
    69	                    <td>{{ $v->violation_date?->format('d/m/Y') }}</td>
    70	                </tr>
    71	            @endforeach
    72	        </tbody>
    73	    </table>
    74	
    75	    <div class="footer">
    76	        <p>تم إنشاء هذا التقرير تلقائياً — نظام إدارة النادي — {{ $generated_at->format('d/m/Y H:i:s') }}</p>
    77	    </div>
    78	</body>
    79	</html>│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [478]: resources/views/reports/violations.blade.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [479/494]: routes/api.php
│ LANGUAGE: php | LINES: 20 | SIZE: 796 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	use App\Http\Controllers\Api\V1\CardVerificationController;
     4	use App\Http\Controllers\Api\V1\MemberLookupController;
     5	use Illuminate\Support\Facades\Route;
     6	
     7	Route::prefix('v1')->middleware(['throttle:60,1'])->group(function () {
     8	
     9	    // Public endpoint: verify card by QR code data
    10	    Route::get('/cards/verify/{qrCode}', [CardVerificationController::class, 'verify'])
    11	        ->name('api.v1.cards.verify');
    12	
    13	    // Authenticated endpoints
    14	    Route::middleware('auth:sanctum')->group(function () {
    15	        Route::get('/members/{membershipNumber}', [MemberLookupController::class, 'show'])
    16	            ->name('api.v1.members.show');
    17	
    18	        Route::get('/members/{membershipNumber}/status', [MemberLookupController::class, 'status'])
    19	            ->name('api.v1.members.status');
    20	    });
    21	});│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [479]: routes/api.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [480/494]: routes/console.php
│ LANGUAGE: php | LINES: 6 | SIZE: 231 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	// Add these to your existing schedule
     4	use Illuminate\Support\Facades\Schedule;
     5	
     6	Schedule::command('disciplinary:expire-suspensions')->dailyAt('01:00');
     7	Schedule::command('disciplinary:complete-penalties')->dailyAt('01:30');│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [480]: routes/console.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [481/494]: routes/member-routes.php
│ LANGUAGE: php | LINES: 26 | SIZE: 984 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	use Illuminate\Support\Facades\Route;
     4	use App\Http\Controllers\MemberPrintController;
     5	use App\Http\Controllers\CardController;
     6	use App\Http\Controllers\ReceiptController;
     7	
     8	// Member print routes (protected by auth)
     9	Route::middleware(['auth', 'verified'])->group(function () {
    10	    Route::get('/members/{member}/print-profile', [MemberPrintController::class, 'profile'])
    11	        ->name('members.print-profile');
    12	
    13	    Route::get('/members/{member}/print-card', [MemberPrintController::class, 'card'])
    14	        ->name('members.print-card');
    15	
    16	    Route::get('/receipts/{receipt}', [ReceiptController::class, 'show'])
    17	        ->name('receipts.show');
    18	
    19	    Route::get('/receipts/{receipt}/print', [ReceiptController::class, 'print'])
    20	        ->name('receipts.print');
    21	
    22	    Route::get('/cards/{card}/preview', [CardController::class, 'preview'])
    23	        ->name('cards.preview');
    24	
    25	    Route::get('/cards/{card}/print', [CardController::class, 'print'])
    26	        ->name('cards.print');
    27	});│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [481]: routes/member-routes.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [482/494]: routes/web.php
│ LANGUAGE: php | LINES: 15 | SIZE: 584 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	use Illuminate\Support\Facades\Route;
     4	
     5	// Filament handles the admin panel routes automatically.
     6	// Only adding custom routes here.
     7	
     8	Route::get('/receipts/{receipt}/print', function (\App\Models\Receipt $receipt) {
     9	    $receiptService = app(\App\Services\Financial\ReceiptService::class);
    10	    $data = $receiptService->getReceiptPrintData($receipt);
    11	
    12	    $pdf = \Barryvdh\DomPDF\Facade\Pdf::loadView('pdf.receipt', $data);
    13	    $pdf->setPaper('a5', 'portrait');
    14	
    15	    return $pdf->stream("receipt-{$receipt->receipt_number}.pdf");
    16	})->middleware(['auth'])->name('receipts.print');│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [482]: routes/web.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [483/494]: scripts/cleanup-root-junk.sh
│ LANGUAGE: bash | LINES: 13 | SIZE: 364 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	#!/bin/bash
     2	# Remove scratch code files from project root
     3	# Run from project root: bash scripts/cleanup-root-junk.sh
     4	
     5	echo "🧹 Cleaning up scratch files from project root..."
     6	
     7	for f in code.txt code_*.txt; do
     8	    if [ -f "$f" ]; then
     9	        echo "  🗑️  Removing $f"
    10	        rm "$f"
    11	    fi
    12	done
    13	
    14	echo "✅ Done. Your root directory is no longer a landfill."│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [483]: scripts/cleanup-root-junk.sh


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [484/494]: tailwind.config.js
│ LANGUAGE: javascript | LINES: 30 | SIZE: 916 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	import preset from './vendor/filament/filament/tailwind.config.preset';
     2	
     3	/** @type {import('tailwindcss').Config} */
     4	export default {
     5	    presets: [preset],
     6	    content: [
     7	        './app/Filament/**/*.php',
     8	        './resources/views/filament/**/*.blade.php',
     9	        './resources/views/**/*.blade.php',
    10	        './vendor/filament/**/*.blade.php',
    11	    ],
    12	    theme: {
    13	        extend: {
    14	            fontFamily: {
    15	                sans: ['Cairo', 'Tajawal', 'sans-serif'],
    16	                mono: ['IBM Plex Mono', 'monospace'],
    17	            },
    18	            colors: {
    19	                club: {
    20	                    primary: '#1a5276',
    21	                    secondary: '#2e86c1',
    22	                    accent: '#f39c12',
    23	                    success: '#27ae60',
    24	                    danger: '#e74c3c',
    25	                    warning: '#f1c40f',
    26	                    info: '#3498db',
    27	                },
    28	            },
    29	        },
    30	    },
    31	};│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [484]: tailwind.config.js


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [485/494]: tests/Feature/Controllers/CardControllerTest.php
│ LANGUAGE: php | LINES: 22 | SIZE: 583 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	use App\Models\Card;
     4	use App\Models\User;
     5	
     6	test('card preview requires authentication', function () {
     7	    $this->seedLookupTables();
     8	
     9	    $card = Card::factory()->create();
    10	
    11	    $this->get(route('cards.preview', $card))
    12	        ->assertRedirect('/login');
    13	});
    14	
    15	test('card print logs audit trail', function () {
    16	    $this->seedLookupTables();
    17	
    18	    $user = User::factory()->create();
    19	    $card = Card::factory()->create();
    20	
    21	    // This test validates the audit logging exists
    22	    expect(fn () => activity()->performedOn($card)->log('test'))->not->toThrow(\Exception::class);
    23	});│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [485]: tests/Feature/Controllers/CardControllerTest.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [486/494]: tests/Feature/Controllers/ReceiptControllerTest.php
│ LANGUAGE: php | LINES: 34 | SIZE: 984 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	use App\Models\Receipt;
     4	use App\Models\User;
     5	
     6	test('receipt show requires authentication', function () {
     7	    $this->seedLookupTables();
     8	
     9	    $receipt = Receipt::factory()->create();
    10	
    11	    $this->get(route('receipts.show', $receipt))
    12	        ->assertRedirect('/login');
    13	});
    14	
    15	test('authenticated user can view receipt', function () {
    16	    $this->seedLookupTables();
    17	
    18	    $user = User::factory()->create();
    19	    $receipt = Receipt::factory()->create();
    20	
    21	    $this->actingAs($user)
    22	        ->get(route('receipts.show', $receipt))
    23	        ->assertOk();
    24	})->skip(fn () => ! Route::has('receipts.show'), 'Route not defined yet');
    25	
    26	test('receipt print requires authorization', function () {
    27	    $this->seedLookupTables();
    28	
    29	    $user = User::factory()->create();
    30	    $receipt = Receipt::factory()->create();
    31	
    32	    $this->actingAs($user)
    33	        ->get(route('receipts.print', $receipt))
    34	        ->assertForbidden();
    35	})->skip(fn () => ! Route::has('receipts.print'), 'Route not defined yet');│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [486]: tests/Feature/Controllers/ReceiptControllerTest.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [487/494]: tests/Feature/Imports/MembersImportTest.php
│ LANGUAGE: php | LINES: 28 | SIZE: 842 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	use App\Imports\MembersImport;
     4	use App\Models\User;
     5	
     6	test('members import class is instantiable', function () {
     7	    $this->seedLookupTables();
     8	
     9	    $user = User::factory()->create();
    10	    $import = new MembersImport(importedBy: $user->id);
    11	
    12	    expect($import)->toBeInstanceOf(MembersImport::class)
    13	        ->and($import->getSuccessCount())->toBe(0)
    14	        ->and($import->getFailCount())->toBe(0)
    15	        ->and($import->getErrors())->toBeArray()->toBeEmpty();
    16	});
    17	
    18	test('members import validates required fields', function () {
    19	    $this->seedLookupTables();
    20	
    21	    $user = User::factory()->create();
    22	    $import = new MembersImport(importedBy: $user->id);
    23	
    24	    $rules = $import->rules();
    25	
    26	    expect($rules)->toHaveKey('membership_number')
    27	        ->and($rules)->toHaveKey('name_ar')
    28	        ->and($rules)->toHaveKey('national_id');
    29	});│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [487]: tests/Feature/Imports/MembersImportTest.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [488/494]: tests/Pest.php
│ LANGUAGE: php | LINES: 11 | SIZE: 186 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	use Illuminate\Foundation\Testing\RefreshDatabase;
     4	
     5	uses(
     6	    Tests\TestCase::class,
     7	    RefreshDatabase::class,
     8	)->in('Feature');
     9	
    10	uses(
    11	    Tests\TestCase::class,
    12	)->in('Unit');│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [488]: tests/Pest.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [489/494]: tests/TestCase.php
│ LANGUAGE: php | LINES: 18 | SIZE: 350 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	namespace Tests;
     4	
     5	use Database\Seeders\DatabaseSeeder;
     6	use Illuminate\Foundation\Testing\TestCase as BaseTestCase;
     7	
     8	abstract class TestCase extends BaseTestCase
     9	{
    10	    protected function setUp(): void
    11	    {
    12	        parent::setUp();
    13	    }
    14	
    15	    protected function seedLookupTables(): void
    16	    {
    17	        $this->seed(DatabaseSeeder::class);
    18	    }
    19	}│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [489]: tests/TestCase.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [490/494]: tests/Unit/Helpers/MoneyHelperTest.php
│ LANGUAGE: php | LINES: 18 | SIZE: 488 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	use App\Helpers\MoneyHelper;
     4	
     5	test('formats money with arabic locale', function () {
     6	    $formatted = MoneyHelper::format(1500.50);
     7	    expect($formatted)->toBeString()
     8	        ->and($formatted)->toContain('1');
     9	});
    10	
    11	test('formats zero correctly', function () {
    12	    $formatted = MoneyHelper::format(0);
    13	    expect($formatted)->toBeString();
    14	});
    15	
    16	test('handles negative amounts', function () {
    17	    $formatted = MoneyHelper::format(-500.00);
    18	    expect($formatted)->toBeString();
    19	});│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [490]: tests/Unit/Helpers/MoneyHelperTest.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [491/494]: tests/Unit/Helpers/NationalIdHelperTest.php
│ LANGUAGE: php | LINES: 21 | SIZE: 623 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	use App\Helpers\NationalIdHelper;
     4	
     5	test('parses valid egyptian national id', function () {
     6	    // Male born 1990-05-15, governorate 01
     7	    $result = NationalIdHelper::parse('29005150100001');
     8	
     9	    expect($result)->toBeArray()
    10	        ->and($result['date_of_birth'])->toBe('1990-05-15')
    11	        ->and($result['gender'])->not->toBeNull();
    12	});
    13	
    14	test('returns null for invalid national id', function () {
    15	    $result = NationalIdHelper::parse('123');
    16	    expect($result)->toBeNull();
    17	});
    18	
    19	test('returns null for empty string', function () {
    20	    $result = NationalIdHelper::parse('');
    21	    expect($result)->toBeNull();
    22	});│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [491]: tests/Unit/Helpers/NationalIdHelperTest.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [492/494]: tests/Unit/Models/MemberTest.php
│ LANGUAGE: php | LINES: 34 | SIZE: 968 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	use App\Models\Member;
     4	
     5	test('member factory creates valid instance', function () {
     6	    $this->seedLookupTables();
     7	
     8	    $member = Member::factory()->create();
     9	
    10	    expect($member)->toBeInstanceOf(Member::class)
    11	        ->and($member->membership_number)->toBeString()
    12	        ->and($member->name_ar)->toBeString()
    13	        ->and($member->national_id)->toHaveLength(14)
    14	        ->and($member->is_archived)->toBeFalse();
    15	});
    16	
    17	test('member can be created as active', function () {
    18	    $this->seedLookupTables();
    19	
    20	    $member = Member::factory()->active()->create();
    21	
    22	    expect($member->activation_date)->not->toBeNull()
    23	        ->and($member->status->code ?? $member->status_id)->not->toBeNull();
    24	});
    25	
    26	test('member can have dependents', function () {
    27	    $this->seedLookupTables();
    28	
    29	    $member = Member::factory()
    30	        ->active()
    31	        ->has(\App\Models\Dependent::factory()->count(3))
    32	        ->create();
    33	
    34	    expect($member->dependents)->toHaveCount(3);
    35	});│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [492]: tests/Unit/Models/MemberTest.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [493/494]: tests/Unit/Services/FeeCalculationServiceTest.php
│ LANGUAGE: php | LINES: 10 | SIZE: 283 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	<?php
     2	
     3	use App\Services\Financial\FeeCalculationService;
     4	
     5	test('fee calculation service exists and is resolvable', function () {
     6	    $this->seedLookupTables();
     7	
     8	    $service = app(FeeCalculationService::class);
     9	
    10	    expect($service)->toBeInstanceOf(FeeCalculationService::class);
    11	});│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [493]: tests/Unit/Services/FeeCalculationServiceTest.php


┌──────────────────────────────────────────────────────────────────────────────
│ 📄 FILE [494/494]: vite.config.js
│ LANGUAGE: javascript | LINES: 18 | SIZE: 499 bytes
├──────────────────────────────────────────────────────────────────────────────
│
     1	import { defineConfig } from 'vite';
     2	import laravel, { refreshPaths } from 'laravel-vite-plugin';
     3	
     4	export default defineConfig({
     5	    plugins: [
     6	        laravel({
     7	            input: [
     8	                'resources/css/app.css',
     9	                'resources/js/app.js',
    10	                'resources/css/filament/admin/theme.css',
    11	            ],
    12	            refresh: [
    13	                ...refreshPaths,
    14	                'app/Filament/**',
    15	                'app/Livewire/**',
    16	            ],
    17	        }),
    18	    ],
    19	});│
└──────────────────────────────────────────────────────────────────────────────
   ✅ END OF [494]: vite.config.js



################################################################################
#                                                                              #
#                     ✅ END OF COMPLETE CODEBASE DUMP                         #
#                                                                              #
#  Total Files:  494                                                        #
#  Total Lines:  41346                                                      #
#  Total Size:   1.4MB                                                      #
#  Generated:    2026-03-17 21:52:02                                        #
#                                                                              #
#  This file contains EVERYTHING: source code, configs, env vars, Docker,      #
#  CI/CD, build tools, package manifests, docs — the complete picture.          #
#                                                                              #
################################################################################
