From d76c871cb720e2dcd9850f35c2f724897fee2b18 Mon Sep 17 00:00:00 2001 From: root Date: Mon, 9 Mar 2026 02:52:13 -0400 Subject: [PATCH] add controllers, servoices --- app/Config/Services.php | 2 +- app/Http/Controllers/Api/Api.zip | Bin 240011 -> 0 bytes .../AttendanceTrackingController.php | 2 +- .../Api/Auth/RegisterController.php | 64 + .../Api/ClassPrep/ClassPrepController.php | 53 + .../ClassPreparationController.php | 60 + .../Communication/CommunicationController.php | 178 ++ .../CompetitionScoresController.php | 212 ++ .../Api/Discounts/DiscountController.php | 107 + .../Api/Email/BroadcastEmailController.php | 184 ++ .../Api/Expenses/ExpenseController.php | 180 ++ .../ExtraCharges/ExtraChargesController.php | 143 ++ .../Api/Finance/FinancialController.php | 218 ++ .../Api/Finance/InvoiceController.php | 125 + .../Api/Finance/PaymentController.php | 58 + .../Finance/PaymentEventChargesController.php | 46 + .../Api/Finance/PaymentManualController.php | 81 + .../Finance/PaymentNotificationController.php | 41 + .../Finance/PaymentTransactionController.php | 51 + .../Api/Finance/PaypalPaymentController.php | 52 + .../Finance/PaypalTransactionsController.php | 71 + .../Api/Finance/ReimbursementController.php | 391 +++ .../Api/Grading/GradingController.php | 281 +++ .../Grading/HomeworkTrackingController.php | 45 + .../Api/Incidents/IncidentController.php | 151 ++ .../Api/Reports/FilesController.php | 105 + .../Api/Scores/FinalController.php | 67 + .../Api/Scores/HomeworkController.php | 85 + .../Api/Scores/MidtermController.php | 67 + .../Api/Scores/ParticipationController.php | 70 + .../Api/Scores/ProjectController.php | 86 + .../Controllers/Api/Scores/QuizController.php | 85 + .../Api/Scores/ScoreCommentController.php | 88 + .../Api/Scores/ScoreController.php | 66 + .../Api/Scores/ScorePredictorController.php | 35 + .../Api/System/SemesterRangeController.php | 80 + app/Http/Controllers/Api/UserController.php | 102 + app/Http/Controllers/old/ApiClient.php | 107 + .../old/BroadcastEmailController.php | 256 -- .../Controllers/old/ClassPrepController.php | 307 --- .../old/ClassPreparationController.php | 616 ----- .../old/CommunicationController.php | 172 -- .../old/CompetitionScoresController.php | 441 ---- .../Controllers/old/DiscountController.php | 906 ------- app/Http/Controllers/old/EmailService.php | 96 + .../Controllers/old/ExpenseController.php | 348 --- .../old/ExtraChargesController.php | 588 ----- .../Controllers/old/FeeCalculationService.php | 183 ++ app/Http/Controllers/old/FilesController.php | 304 --- app/Http/Controllers/old/FinalController.php | 159 -- .../Controllers/old/FinancialController.php | 1349 ---------- app/Http/Controllers/old/FlagController.php | 568 ----- .../Controllers/old/GradingController.php | 2237 ----------------- .../Controllers/old/HomeworkController.php | 656 ----- .../old/HomeworkTrackingController.php | 289 --- .../Controllers/old/InvoiceController.php | 1701 ------------- .../Controllers/old/MidtermController.php | 364 --- app/Http/Controllers/old/NavbarService.php | 66 + .../Controllers/old/NotificationService.php | 39 + .../old/ParticipationController.php | 355 --- .../Controllers/old/PaymentController.php | 1775 ------------- .../old/PaymentNotificationController.php | 365 --- .../old/PaymentTransactionController.php | 84 - .../old/PaypalTransactionsController.php | 99 - .../Controllers/old/PhoneFormatterService.php | 31 + .../Controllers/old/ProjectController.php | 540 ---- app/Http/Controllers/old/QuizController.php | 577 ----- .../Controllers/old/RegisterController.php | 360 --- app/Http/Controllers/old/RoleService.php | 21 + app/Http/Controllers/old/SchoolIdService.php | 74 + .../old/ScoreCommentController.php | 550 ---- app/Http/Controllers/old/ScoreController.php | 1438 ----------- app/Http/Controllers/old/ScorePredictor.php | 260 -- app/Http/Controllers/old/TimeService.php | 222 ++ app/Http/Controllers/old/UserController.php | 874 ------- app/Http/Requests/ApiFormRequest.php | 23 + app/Http/Requests/Auth/RegisterRequest.php | 44 + .../Discounts/ApplyDiscountVoucherRequest.php | 23 + .../Discounts/StoreDiscountVoucherRequest.php | 29 + .../UpdateDiscountVoucherRequest.php | 29 + .../Requests/Expenses/StoreExpenseRequest.php | 28 + .../Expenses/UpdateExpenseRequest.php | 27 + .../Expenses/UpdateExpenseStatusRequest.php | 21 + .../ExtraCharges/StoreExtraChargeRequest.php | 28 + .../ExtraCharges/UpdateExtraChargeRequest.php | 24 + app/Http/Requests/Files/FileNameRequest.php | 27 + .../Finance/FinancialReportRequest.php | 22 + .../Finance/FinancialSummaryRequest.php | 7 + .../Finance/FinancialUnpaidParentsRequest.php | 20 + .../Grading/BelowSixtyEmailRequest.php | 22 + .../Grading/BelowSixtyListRequest.php | 21 + .../Grading/BelowSixtyMeetingRequest.php | 22 + .../Grading/BelowSixtyMeetingSaveRequest.php | 25 + .../Grading/BelowSixtySendRequest.php | 24 + .../Grading/BelowSixtyStatusRequest.php | 24 + .../Grading/GradingLockAllRequest.php | 21 + .../Requests/Grading/GradingLockRequest.php | 22 + .../Grading/GradingOverviewRequest.php | 22 + .../Grading/GradingRefreshRequest.php | 22 + .../Grading/GradingScoreShowRequest.php | 21 + .../Grading/GradingScoreUpdateRequest.php | 29 + .../Grading/HomeworkTrackingRequest.php | 22 + .../Grading/PlacementBatchRequest.php | 22 + .../Grading/PlacementBatchUpdateRequest.php | 21 + .../Grading/PlacementLevelRequest.php | 22 + .../Grading/PlacementLevelsRequest.php | 22 + .../Requests/Grading/PlacementRequest.php | 23 + .../Incidents/IncidentCancelRequest.php | 21 + .../Incidents/IncidentCloseRequest.php | 21 + .../Incidents/IncidentListRequest.php | 21 + .../Incidents/IncidentStateRequest.php | 20 + .../Incidents/IncidentStoreRequest.php | 53 + .../Invoices/InvoiceGenerateRequest.php | 21 + .../Invoices/InvoiceManagementRequest.php | 20 + .../Invoices/InvoiceParentRequest.php | 20 + .../Invoices/InvoiceStatusRequest.php | 20 + .../Payments/PaymentByParentRequest.php | 15 + .../Payments/PaymentCreateRequest.php | 23 + .../PaymentEventChargesListRequest.php | 21 + .../PaymentEventChargesStoreRequest.php | 28 + .../Payments/PaymentManualEditRequest.php | 23 + .../Payments/PaymentManualSearchRequest.php | 21 + .../Payments/PaymentManualSuggestRequest.php | 20 + .../Payments/PaymentManualUpdateRequest.php | 27 + .../PaymentNotificationListRequest.php | 22 + .../PaymentNotificationSendRequest.php | 23 + .../PaymentTransactionCreateRequest.php | 30 + .../PaymentTransactionStatusRequest.php | 20 + .../Payments/PaymentUpdateBalanceRequest.php | 15 + .../Payments/PaypalExecuteRequest.php | 21 + .../PaypalTransactionsListRequest.php | 21 + .../ReimbursementBatchAdminFileRequest.php | 22 + .../ReimbursementBatchAssignmentRequest.php | 24 + .../ReimbursementBatchCreateRequest.php | 20 + .../ReimbursementBatchEmailRequest.php | 26 + .../ReimbursementBatchLockRequest.php | 20 + .../ReimbursementExportBatchRequest.php | 20 + .../ReimbursementExportRequest.php | 24 + .../ReimbursementIndexRequest.php | 23 + .../ReimbursementMarkDonationRequest.php | 20 + .../ReimbursementProcessRequest.php | 28 + .../ReimbursementStoreRequest.php | 28 + .../ReimbursementUnderProcessingRequest.php | 18 + .../ReimbursementUpdateRequest.php | 26 + .../Requests/Scores/ScoreAddColumnRequest.php | 22 + .../Requests/Scores/ScoreClassRequest.php | 22 + .../Scores/ScoreCommentListRequest.php | 22 + .../Scores/ScoreCommentSaveRequest.php | 24 + .../Scores/ScoreCommentUpdateRequest.php | 24 + app/Http/Requests/Scores/ScoreLockRequest.php | 23 + .../Requests/Scores/ScoreOverviewRequest.php | 22 + .../Requests/Scores/ScorePredictorRequest.php | 21 + .../Requests/Scores/ScoreStudentRequest.php | 20 + .../Requests/Scores/ScoreUpdateRequest.php | 24 + .../Semesters/SchoolYearRangeRequest.php | 20 + .../Semesters/SemesterNormalizeRequest.php | 20 + .../Semesters/SemesterRangeRequest.php | 21 + .../Semesters/SemesterResolveRequest.php | 20 + .../Requests/Users/LoginActivityRequest.php | 21 + app/Http/Requests/Users/UserListRequest.php | 22 + app/Http/Requests/Users/UserStoreRequest.php | 45 + app/Http/Requests/Users/UserUpdateRequest.php | 45 + app/Http/Resources/Auth/RegisterResource.php | 22 + .../Discounts/DiscountParentResource.php | 20 + .../Discounts/DiscountVoucherResource.php | 26 + .../Resources/Expenses/ExpenseResource.php | 34 + .../Expenses/ExpenseStaffResource.php | 17 + .../ExtraChargeInvoiceOptionResource.php | 20 + .../ExtraChargeParentOptionResource.php | 16 + .../ExtraCharges/ExtraChargeResource.php | 31 + app/Http/Resources/Files/FileMetaResource.php | 21 + .../Finance/FinancialDiscountResource.php | 18 + .../FinancialExpenseSummaryResource.php | 17 + .../Finance/FinancialInvoiceResource.php | 28 + .../Finance/FinancialPaymentResource.php | 17 + .../Finance/FinancialRefundResource.php | 18 + .../FinancialReimbursementSummaryResource.php | 17 + .../Finance/FinancialReportResource.php | 27 + .../Finance/FinancialSummaryResource.php | 29 + .../Finance/FinancialUnpaidParentResource.php | 28 + .../Grading/BelowSixtyStudentResource.php | 31 + .../Grading/GradingScoreResource.php | 14 + .../HomeworkTrackingTeacherResource.php | 20 + .../IncidentAnalysisStudentResource.php | 24 + .../Incidents/IncidentGradeResource.php | 17 + .../Resources/Incidents/IncidentResource.php | 36 + .../IncidentStudentOptionResource.php | 17 + .../InvoiceManagementParentResource.php | 24 + .../Resources/Invoices/InvoiceResource.php | 31 + .../PaymentNotificationLogResource.php | 32 + .../Resources/Payments/PaymentResource.php | 30 + .../Payments/PaymentTransactionResource.php | 26 + .../Payments/PaypalTransactionResource.php | 26 + .../ReimbursementBatchResource.php | 23 + .../ReimbursementExpenseResource.php | 36 + .../ReimbursementRecipientResource.php | 24 + .../Reimbursements/ReimbursementResource.php | 30 + ...imbursementUnderProcessingItemResource.php | 28 + .../Resources/Scores/ScoreCommentResource.php | 20 + .../Scores/ScorePredictorStudentResource.php | 30 + .../Resources/Scores/ScoreStudentResource.php | 21 + .../Semesters/SemesterRangeResource.php | 17 + .../Semesters/SemesterResolveResource.php | 17 + .../Resources/Users/LoginActivityResource.php | 21 + .../Resources/Users/UserWithRolesResource.php | 20 + app/Interfaces/ScoreCalculatorInterface.php | 11 + app/Models/CurrentIncident.php | 40 + app/Models/{Flag.php => Incident.php} | 14 +- app/Models/PaymentTransaction.php | 4 +- .../AdministratorEnrollmentStatusService.php | 6 +- .../Assignment/AssignmentContextService.php | 28 + .../AssignmentDataLoaderService.php | 46 + .../Assignment/AssignmentMetaService.php | 38 + .../Assignment/AssignmentSectionService.php | 27 + app/Services/Assignment/AssignmentService.php | 128 +- .../AttendanceTrackingService.php | 175 +- app/Services/AttendanceTrackingService.php | 9 - .../Auth/RegistrationCaptchaService.php | 36 + .../Auth/RegistrationFormatterService.php | 84 + app/Services/Auth/RegistrationService.php | 159 ++ .../BroadcastEmailComposerService.php | 45 + .../BroadcastEmailDispatchService.php | 81 + .../BroadcastEmailImageService.php | 51 + .../BroadcastEmailRecipientService.php | 36 + .../BroadcastEmailSenderOptionsService.php | 33 + app/Services/ClassPrep/ClassRosterService.php | 42 + .../ClassPrep/StickerCountService.php | 161 ++ .../ClassPreparationAdjustmentService.php | 32 + .../ClassPreparationCalculatorService.php | 126 + .../ClassPreparationContextService.php | 44 + .../ClassPreparationInventoryService.php | 54 + .../ClassPreparationLogService.php | 41 + .../ClassPreparationRosterService.php | 40 + .../ClassPreparationService.php | 121 + .../CommunicationFamilyService.php | 59 + .../Communication/CommunicationLogService.php | 18 + .../CommunicationPreviewService.php | 84 + .../CommunicationSendService.php | 97 + .../CommunicationStudentService.php | 53 + .../CommunicationTemplateService.php | 57 + .../CompetitionScoresContextService.php | 73 + .../CompetitionScoresQueryService.php | 118 + .../CompetitionScoresRosterService.php | 67 + .../CompetitionScoresSaveService.php | 63 + .../Discounts/DiscountApplyService.php | 294 +++ .../Discounts/DiscountContextService.php | 18 + .../Discounts/DiscountInvoiceService.php | 394 +++ .../Discounts/DiscountParentService.php | 27 + .../Discounts/DiscountVoucherService.php | 48 + app/Services/EmailService.php | 63 + .../Expenses/ExpenseContextService.php | 18 + app/Services/Expenses/ExpenseQueryService.php | 41 + .../Expenses/ExpenseReceiptService.php | 22 + .../Expenses/ExpenseRetailorService.php | 34 + app/Services/Expenses/ExpenseStaffService.php | 46 + .../Expenses/ExpenseStatusService.php | 19 + app/Services/Expenses/ExpenseWriteService.php | 41 + .../ExtraChargesChargeService.php | 235 ++ .../ExtraChargesContextService.php | 18 + .../ExtraChargesInvoiceService.php | 75 + .../ExtraCharges/ExtraChargesListService.php | 23 + .../ExtraCharges/ExtraChargesMetaService.php | 66 + .../ExtraChargesParentService.php | 48 + .../Files/ExamDraftDownloadNameService.php | 45 + app/Services/Files/FileServeService.php | 112 + .../Finance/FinancialChartService.php | 90 + .../FinancialDonationRecipientService.php | 16 + .../Finance/FinancialPaymentService.php | 119 + .../Finance/FinancialPdfReportService.php | 62 + .../Finance/FinancialReportService.php | 104 + .../Finance/FinancialSchoolYearService.php | 28 + .../Finance/FinancialSummaryService.php | 479 ++++ .../Finance/FinancialUnpaidParentsService.php | 247 ++ .../Grading/GradingBelowSixtyService.php | 403 +++ app/Services/Grading/GradingLockService.php | 118 + .../Grading/GradingOverviewService.php | 439 ++++ .../Grading/GradingPlacementService.php | 372 +++ .../Grading/GradingRefreshService.php | 111 + app/Services/Grading/GradingScoreService.php | 189 ++ .../HomeworkTrackingCalendarService.php | 86 + .../Grading/HomeworkTrackingService.php | 219 ++ .../Incidents/CurrentIncidentService.php | 233 ++ .../Incidents/IncidentAnalysisService.php | 92 + .../Incidents/IncidentHistoryService.php | 61 + .../Incidents/IncidentLookupService.php | 86 + .../Invoices/InvoiceConfigService.php | 63 + .../Invoices/InvoiceGenerationService.php | 264 ++ app/Services/Invoices/InvoiceGradeService.php | 83 + .../Invoices/InvoiceManagementService.php | 146 ++ .../Invoices/InvoicePaymentService.php | 82 + app/Services/Invoices/InvoicePdfService.php | 445 ++++ .../Invoices/InvoiceTuitionService.php | 77 + .../Payments/PaymentBalanceService.php | 13 + .../PaymentEnrollmentEventService.php | 131 + .../Payments/PaymentEventChargesService.php | 98 + .../Payments/PaymentLookupService.php | 19 + .../Payments/PaymentManualService.php | 649 +++++ .../PaymentNotificationDispatchService.php | 39 + .../Payments/PaymentNotificationService.php | 396 +++ app/Services/Payments/PaymentPlanService.php | 39 + .../Payments/PaymentTransactionService.php | 37 + .../Payments/PaypalPaymentService.php | 123 + .../Payments/PaypalTransactionsService.php | 42 + app/Services/PhoneFormatterService.php | 22 + .../ReimbursementBatchAssignmentService.php | 121 + .../ReimbursementBatchService.php | 154 ++ .../ReimbursementContextService.php | 18 + .../ReimbursementCrudService.php | 97 + .../ReimbursementDonationService.php | 53 + .../ReimbursementEmailService.php | 358 +++ .../ReimbursementExportService.php | 233 ++ .../ReimbursementFileService.php | 119 + .../ReimbursementLookupService.php | 23 + .../ReimbursementQueryService.php | 498 ++++ .../ReimbursementRecipientService.php | 93 + app/Services/SchoolIdService.php | 69 + app/Services/Scores/AttendanceCalculator.php | 146 ++ app/Services/Scores/ExamScoreService.php | 129 + app/Services/Scores/HomeworkCalculator.php | 19 + app/Services/Scores/HomeworkScoreService.php | 226 ++ .../Scores/ParticipationScoreService.php | 146 ++ app/Services/Scores/ProjectCalculator.php | 19 + app/Services/Scores/ProjectScoreService.php | 223 ++ app/Services/Scores/QuizCalculator.php | 38 + app/Services/Scores/QuizScoreService.php | 220 ++ app/Services/Scores/ScoreCommentService.php | 340 +++ app/Services/Scores/ScoreDashboardService.php | 243 ++ .../Scores/ScorePredictorDataService.php | 105 + .../Scores/ScorePredictorRiskService.php | 143 ++ app/Services/Scores/ScorePredictorService.php | 41 + app/Services/Scores/ScoreTermService.php | 70 + app/Services/Scores/SemesterScoreService.php | 227 ++ app/Services/SemesterRangeService.php | 113 + .../Semesters/SemesterConfigService.php | 41 + app/Services/Users/LoginActivityService.php | 45 + app/Services/Users/UserEventService.php | 31 + app/Services/Users/UserListService.php | 161 ++ app/Services/Users/UserManagementService.php | 293 +++ app/Support/SqliteCompat.php | 1 + composer.json | 1 + composer.lock | 65 +- config/sanctum.php | 84 + ...3_204035_create_current_incident_table.php | 51 + ...26_02_23_204036_create_incident_table.php} | 14 +- ...900056_create_communication_logs_table.php | 53 + ...12_create_personal_access_tokens_table.php | 33 + ...026_03_09_064908_create_sessions_table.php | 31 + routes/api.php | 283 ++- school_api | Bin 0 -> 622592 bytes storage/testing/files/cached.pdf | 1 + storage/testing/files/sample.exe | 1 + storage/testing/files/sample.pdf | 1 + .../AttendanceTrackingControllerTest.php | 4 +- .../Api/V1/Auth/RegisterControllerTest.php | 71 + .../Api/V1/Auth/RegisterPayloadDebugTest.php | 40 + .../BroadcastEmailControllerTest.php | 134 + .../V1/ClassPrep/ClassPrepControllerTest.php | 203 ++ .../ClassPreparationControllerTest.php | 134 + .../CommunicationControllerTest.php | 186 ++ .../CompetitionScoresControllerTest.php | 178 ++ .../V1/Discounts/DiscountControllerTest.php | 198 ++ .../Api/V1/Expenses/ExpenseControllerTest.php | 236 ++ .../ExtraChargesControllerTest.php | 261 ++ .../V1/Finance/FinancialControllerTest.php | 193 ++ .../Api/V1/Finance/InvoiceControllerTest.php | 166 ++ .../Api/V1/Finance/PaymentControllerTest.php | 153 ++ .../PaymentEventChargesControllerTest.php | 108 + .../Finance/PaymentManualControllerTest.php | 132 + .../PaymentNotificationControllerTest.php | 141 ++ .../PaymentTransactionControllerTest.php | 136 + .../PaypalTransactionsControllerTest.php | 98 + .../Finance/ReimbursementControllerTest.php | 185 ++ .../Api/V1/Grading/GradingControllerTest.php | 129 + .../HomeworkTrackingControllerTest.php | 79 + .../V1/Incidents/IncidentControllerTest.php | 158 ++ .../Api/V1/Reports/FilesControllerTest.php | 100 + .../Api/V1/Scores/FinalControllerTest.php | 91 + .../Api/V1/Scores/HomeworkControllerTest.php | 92 + .../Api/V1/Scores/MidtermControllerTest.php | 91 + .../V1/Scores/ParticipationControllerTest.php | 104 + .../Api/V1/Scores/ProjectControllerTest.php | 92 + .../Api/V1/Scores/QuizControllerTest.php | 92 + .../V1/Scores/ScoreCommentControllerTest.php | 111 + .../Api/V1/Scores/ScoreControllerTest.php | 117 + .../Scores/ScorePredictorControllerTest.php | 108 + .../V1/System/SemesterRangeControllerTest.php | 74 + .../Api/V1/Users/UserControllerTest.php | 153 ++ .../AdminNotificationSubjectServiceTest.php | 144 +- .../AdminNotificationUserServiceTest.php | 75 + .../AdminPrintRecipientServiceTest.php | 138 +- .../AdministratorAbsenceServiceTest.php | 36 + .../AdministratorDashboardServiceTest.php | 27 + ...dministratorEnrollmentEventServiceTest.php | 23 + ...dministratorEnrollmentQueryServiceTest.php | 264 +- ...ministratorEnrollmentRefundServiceTest.php | 34 + .../AdministratorEnrollmentServiceTest.php | 29 + ...ministratorEnrollmentStatusServiceTest.php | 227 +- .../AdministratorMetricsServiceTest.php | 32 + .../AdministratorNotificationServiceTest.php | 31 + .../AdministratorSharedServiceTest.php | 21 + ...inistratorTeacherSubmissionServiceTest.php | 27 + .../AdministratorUserSearchServiceTest.php | 22 + ...acherSubmissionNotificationServiceTest.php | 159 +- .../TeacherSubmissionReportServiceTest.php | 327 +-- .../TeacherSubmissionSupportServiceTest.php | 125 +- .../AssignmentContextServiceTest.php | 27 + .../AssignmentDataLoaderServiceTest.php | 118 + .../Assignment/AssignmentMetaServiceTest.php | 43 + .../AssignmentSectionServiceTest.php | 31 + .../AttendancePolicyServiceTest.php | 55 +- .../Attendance/AttendanceQueryServiceTest.php | 111 + .../AttendanceRecordSyncServiceTest.php | 262 +- .../AttendanceSemesterRangeServiceTest.php | 29 + .../Attendance/AttendanceServiceTest.php | 128 + .../Attendance/StaffAttendanceServiceTest.php | 90 + .../StudentAttendanceWriterServiceTest.php | 343 +-- ...TeacherAttendanceSubmissionServiceTest.php | 556 +--- .../AttendanceCaseQueryServiceTest.php | 40 +- ...endanceCommunicationSupportServiceTest.php | 90 +- .../AttendanceEmailComposerServiceTest.php | 129 +- .../AttendanceNotificationLogServiceTest.php | 59 +- ...endanceNotificationWorkflowServiceTest.php | 132 +- .../AttendanceParentLookupServiceTest.php | 114 +- .../AttendancePendingViolationServiceTest.php | 58 +- .../AttendanceTrackingServiceTest.php | 133 +- ...nceViolationStudentResolverServiceTest.php | 87 +- .../ViolationRuleEngineServiceTest.php | 108 +- .../Auth/RegistrationCaptchaServiceTest.php | 33 + .../Auth/RegistrationFormatterServiceTest.php | 70 + .../Services/Auth/RegistrationServiceTest.php | 150 ++ .../BroadcastEmailComposerServiceTest.php | 30 + .../BroadcastEmailRecipientServiceTest.php | 93 + ...BroadcastEmailSenderOptionsServiceTest.php | 23 + .../ClassPrep/ClassRosterServiceTest.php | 71 + .../ClassPrep/StickerCountServiceTest.php | 165 ++ .../ClassPreparationAdjustmentServiceTest.php | 30 + .../ClassPreparationCalculatorServiceTest.php | 59 + .../ClassPreparationContextServiceTest.php | 28 + .../ClassPreparationInventoryServiceTest.php | 48 + .../ClassPreparationLogServiceTest.php | 36 + .../ClassPreparationRosterServiceTest.php | 84 + .../ClassPreparationServiceTest.php | 93 + .../CommunicationTemplateServiceTest.php | 31 + .../CompetitionScoresSaveServiceTest.php | 46 + .../Discounts/DiscountApplyServiceTest.php | 38 + .../Expenses/ExpenseReceiptServiceTest.php | 26 + .../Expenses/ExpenseStaffServiceTest.php | 75 + .../ExtraChargesMetaServiceTest.php | 61 + .../ExamDraftDownloadNameServiceTest.php | 43 + .../Services/Files/FileServeServiceTest.php | 69 + .../Finance/FinancialChartServiceTest.php | 31 + .../Finance/FinancialPaymentServiceTest.php | 94 + .../Finance/FinancialPdfReportServiceTest.php | 37 + .../Finance/FinancialReportServiceTest.php | 126 + .../FinancialSchoolYearServiceTest.php | 50 + .../Finance/FinancialSummaryServiceTest.php | 165 ++ .../FinancialUnpaidParentsServiceTest.php | 110 + .../Grading/GradingBelowSixtyServiceTest.php | 55 + .../Grading/GradingLockServiceTest.php | 69 + .../Grading/HomeworkTrackingServiceTest.php | 81 + .../Incidents/IncidentAnalysisServiceTest.php | 62 + .../Incidents/IncidentHistoryServiceTest.php | 66 + .../Incidents/IncidentLookupServiceTest.php | 79 + .../Invoices/InvoiceConfigServiceTest.php | 38 + .../Invoices/InvoiceGenerationServiceTest.php | 92 + .../Invoices/InvoiceGradeServiceTest.php | 37 + .../Invoices/InvoiceManagementServiceTest.php | 107 + .../Invoices/InvoicePaymentServiceTest.php | 63 + .../Invoices/InvoicePdfServiceTest.php | 91 + .../Invoices/InvoiceTuitionServiceTest.php | 60 + .../Payments/PaymentBalanceServiceTest.php | 40 + .../PaymentEnrollmentEventServiceTest.php | 53 + .../PaymentEventChargesServiceTest.php | 82 + .../Payments/PaymentLookupServiceTest.php | 50 + .../Payments/PaymentManualServiceTest.php | 61 + ...PaymentNotificationDispatchServiceTest.php | 40 + .../PaymentNotificationServiceTest.php | 134 + .../Payments/PaymentPlanServiceTest.php | 70 + .../PaymentTransactionServiceTest.php | 67 + .../Payments/PaypalPaymentServiceTest.php | 45 + .../PaypalTransactionsServiceTest.php | 46 + ...eimbursementBatchAssignmentServiceTest.php | 83 + .../ReimbursementBatchServiceTest.php | 30 + .../ReimbursementDonationServiceTest.php | 77 + .../ReimbursementRecipientServiceTest.php | 79 + .../Services/Scores/ExamScoreServiceTest.php | 106 + .../Scores/HomeworkScoreServiceTest.php | 81 + .../Scores/ParticipationScoreServiceTest.php | 98 + .../Scores/ProjectScoreServiceTest.php | 114 + .../Services/Scores/QuizScoreServiceTest.php | 114 + .../Scores/ScoreCommentServiceTest.php | 80 + .../Scores/ScoreDashboardServiceTest.php | 110 + .../Scores/ScorePredictorServiceTest.php | 77 + .../Services/Scores/ScoreTermServiceTest.php | 38 + .../Scores/SemesterScoreServiceTest.php | 107 + .../Semesters/SemesterConfigServiceTest.php | 27 + .../Semesters/SemesterRangeServiceTest.php | 46 + .../Users/LoginActivityServiceTest.php | 45 + .../Services/Users/UserEventServiceTest.php | 52 + .../Services/Users/UserListServiceTest.php | 73 + .../Users/UserManagementServiceTest.php | 153 ++ 501 files changed, 34439 insertions(+), 21843 deletions(-) delete mode 100644 app/Http/Controllers/Api/Api.zip create mode 100644 app/Http/Controllers/Api/Auth/RegisterController.php create mode 100644 app/Http/Controllers/Api/ClassPrep/ClassPrepController.php create mode 100644 app/Http/Controllers/Api/ClassPreparation/ClassPreparationController.php create mode 100644 app/Http/Controllers/Api/Communication/CommunicationController.php create mode 100644 app/Http/Controllers/Api/CompetitionScores/CompetitionScoresController.php create mode 100644 app/Http/Controllers/Api/Discounts/DiscountController.php create mode 100644 app/Http/Controllers/Api/Email/BroadcastEmailController.php create mode 100644 app/Http/Controllers/Api/Expenses/ExpenseController.php create mode 100644 app/Http/Controllers/Api/ExtraCharges/ExtraChargesController.php create mode 100644 app/Http/Controllers/Api/Finance/FinancialController.php create mode 100644 app/Http/Controllers/Api/Finance/InvoiceController.php create mode 100644 app/Http/Controllers/Api/Finance/PaymentController.php create mode 100644 app/Http/Controllers/Api/Finance/PaymentEventChargesController.php create mode 100644 app/Http/Controllers/Api/Finance/PaymentManualController.php create mode 100644 app/Http/Controllers/Api/Finance/PaymentNotificationController.php create mode 100644 app/Http/Controllers/Api/Finance/PaymentTransactionController.php create mode 100644 app/Http/Controllers/Api/Finance/PaypalPaymentController.php create mode 100644 app/Http/Controllers/Api/Finance/PaypalTransactionsController.php create mode 100644 app/Http/Controllers/Api/Finance/ReimbursementController.php create mode 100644 app/Http/Controllers/Api/Grading/GradingController.php create mode 100644 app/Http/Controllers/Api/Grading/HomeworkTrackingController.php create mode 100644 app/Http/Controllers/Api/Incidents/IncidentController.php create mode 100644 app/Http/Controllers/Api/Reports/FilesController.php create mode 100644 app/Http/Controllers/Api/Scores/FinalController.php create mode 100644 app/Http/Controllers/Api/Scores/HomeworkController.php create mode 100644 app/Http/Controllers/Api/Scores/MidtermController.php create mode 100644 app/Http/Controllers/Api/Scores/ParticipationController.php create mode 100644 app/Http/Controllers/Api/Scores/ProjectController.php create mode 100644 app/Http/Controllers/Api/Scores/QuizController.php create mode 100644 app/Http/Controllers/Api/Scores/ScoreCommentController.php create mode 100644 app/Http/Controllers/Api/Scores/ScoreController.php create mode 100644 app/Http/Controllers/Api/Scores/ScorePredictorController.php create mode 100644 app/Http/Controllers/Api/System/SemesterRangeController.php create mode 100644 app/Http/Controllers/Api/UserController.php create mode 100644 app/Http/Controllers/old/ApiClient.php delete mode 100644 app/Http/Controllers/old/BroadcastEmailController.php delete mode 100644 app/Http/Controllers/old/ClassPrepController.php delete mode 100644 app/Http/Controllers/old/ClassPreparationController.php delete mode 100644 app/Http/Controllers/old/CommunicationController.php delete mode 100644 app/Http/Controllers/old/CompetitionScoresController.php delete mode 100644 app/Http/Controllers/old/DiscountController.php create mode 100644 app/Http/Controllers/old/EmailService.php delete mode 100644 app/Http/Controllers/old/ExpenseController.php delete mode 100644 app/Http/Controllers/old/ExtraChargesController.php create mode 100644 app/Http/Controllers/old/FeeCalculationService.php delete mode 100644 app/Http/Controllers/old/FilesController.php delete mode 100644 app/Http/Controllers/old/FinalController.php delete mode 100644 app/Http/Controllers/old/FinancialController.php delete mode 100644 app/Http/Controllers/old/FlagController.php delete mode 100644 app/Http/Controllers/old/GradingController.php delete mode 100644 app/Http/Controllers/old/HomeworkController.php delete mode 100644 app/Http/Controllers/old/HomeworkTrackingController.php delete mode 100644 app/Http/Controllers/old/InvoiceController.php delete mode 100644 app/Http/Controllers/old/MidtermController.php create mode 100644 app/Http/Controllers/old/NavbarService.php create mode 100644 app/Http/Controllers/old/NotificationService.php delete mode 100644 app/Http/Controllers/old/ParticipationController.php delete mode 100644 app/Http/Controllers/old/PaymentController.php delete mode 100644 app/Http/Controllers/old/PaymentNotificationController.php delete mode 100644 app/Http/Controllers/old/PaymentTransactionController.php delete mode 100644 app/Http/Controllers/old/PaypalTransactionsController.php create mode 100644 app/Http/Controllers/old/PhoneFormatterService.php delete mode 100644 app/Http/Controllers/old/ProjectController.php delete mode 100644 app/Http/Controllers/old/QuizController.php delete mode 100644 app/Http/Controllers/old/RegisterController.php create mode 100644 app/Http/Controllers/old/RoleService.php create mode 100644 app/Http/Controllers/old/SchoolIdService.php delete mode 100644 app/Http/Controllers/old/ScoreCommentController.php delete mode 100644 app/Http/Controllers/old/ScoreController.php delete mode 100644 app/Http/Controllers/old/ScorePredictor.php create mode 100644 app/Http/Controllers/old/TimeService.php delete mode 100644 app/Http/Controllers/old/UserController.php create mode 100644 app/Http/Requests/Auth/RegisterRequest.php create mode 100644 app/Http/Requests/Discounts/ApplyDiscountVoucherRequest.php create mode 100644 app/Http/Requests/Discounts/StoreDiscountVoucherRequest.php create mode 100644 app/Http/Requests/Discounts/UpdateDiscountVoucherRequest.php create mode 100644 app/Http/Requests/Expenses/StoreExpenseRequest.php create mode 100644 app/Http/Requests/Expenses/UpdateExpenseRequest.php create mode 100644 app/Http/Requests/Expenses/UpdateExpenseStatusRequest.php create mode 100644 app/Http/Requests/ExtraCharges/StoreExtraChargeRequest.php create mode 100644 app/Http/Requests/ExtraCharges/UpdateExtraChargeRequest.php create mode 100644 app/Http/Requests/Files/FileNameRequest.php create mode 100644 app/Http/Requests/Finance/FinancialReportRequest.php create mode 100644 app/Http/Requests/Finance/FinancialSummaryRequest.php create mode 100644 app/Http/Requests/Finance/FinancialUnpaidParentsRequest.php create mode 100644 app/Http/Requests/Grading/BelowSixtyEmailRequest.php create mode 100644 app/Http/Requests/Grading/BelowSixtyListRequest.php create mode 100644 app/Http/Requests/Grading/BelowSixtyMeetingRequest.php create mode 100644 app/Http/Requests/Grading/BelowSixtyMeetingSaveRequest.php create mode 100644 app/Http/Requests/Grading/BelowSixtySendRequest.php create mode 100644 app/Http/Requests/Grading/BelowSixtyStatusRequest.php create mode 100644 app/Http/Requests/Grading/GradingLockAllRequest.php create mode 100644 app/Http/Requests/Grading/GradingLockRequest.php create mode 100644 app/Http/Requests/Grading/GradingOverviewRequest.php create mode 100644 app/Http/Requests/Grading/GradingRefreshRequest.php create mode 100644 app/Http/Requests/Grading/GradingScoreShowRequest.php create mode 100644 app/Http/Requests/Grading/GradingScoreUpdateRequest.php create mode 100644 app/Http/Requests/Grading/HomeworkTrackingRequest.php create mode 100644 app/Http/Requests/Grading/PlacementBatchRequest.php create mode 100644 app/Http/Requests/Grading/PlacementBatchUpdateRequest.php create mode 100644 app/Http/Requests/Grading/PlacementLevelRequest.php create mode 100644 app/Http/Requests/Grading/PlacementLevelsRequest.php create mode 100644 app/Http/Requests/Grading/PlacementRequest.php create mode 100644 app/Http/Requests/Incidents/IncidentCancelRequest.php create mode 100644 app/Http/Requests/Incidents/IncidentCloseRequest.php create mode 100644 app/Http/Requests/Incidents/IncidentListRequest.php create mode 100644 app/Http/Requests/Incidents/IncidentStateRequest.php create mode 100644 app/Http/Requests/Incidents/IncidentStoreRequest.php create mode 100644 app/Http/Requests/Invoices/InvoiceGenerateRequest.php create mode 100644 app/Http/Requests/Invoices/InvoiceManagementRequest.php create mode 100644 app/Http/Requests/Invoices/InvoiceParentRequest.php create mode 100644 app/Http/Requests/Invoices/InvoiceStatusRequest.php create mode 100644 app/Http/Requests/Payments/PaymentByParentRequest.php create mode 100644 app/Http/Requests/Payments/PaymentCreateRequest.php create mode 100644 app/Http/Requests/Payments/PaymentEventChargesListRequest.php create mode 100644 app/Http/Requests/Payments/PaymentEventChargesStoreRequest.php create mode 100644 app/Http/Requests/Payments/PaymentManualEditRequest.php create mode 100644 app/Http/Requests/Payments/PaymentManualSearchRequest.php create mode 100644 app/Http/Requests/Payments/PaymentManualSuggestRequest.php create mode 100644 app/Http/Requests/Payments/PaymentManualUpdateRequest.php create mode 100644 app/Http/Requests/Payments/PaymentNotificationListRequest.php create mode 100644 app/Http/Requests/Payments/PaymentNotificationSendRequest.php create mode 100644 app/Http/Requests/Payments/PaymentTransactionCreateRequest.php create mode 100644 app/Http/Requests/Payments/PaymentTransactionStatusRequest.php create mode 100644 app/Http/Requests/Payments/PaymentUpdateBalanceRequest.php create mode 100644 app/Http/Requests/Payments/PaypalExecuteRequest.php create mode 100644 app/Http/Requests/Payments/PaypalTransactionsListRequest.php create mode 100644 app/Http/Requests/Reimbursements/ReimbursementBatchAdminFileRequest.php create mode 100644 app/Http/Requests/Reimbursements/ReimbursementBatchAssignmentRequest.php create mode 100644 app/Http/Requests/Reimbursements/ReimbursementBatchCreateRequest.php create mode 100644 app/Http/Requests/Reimbursements/ReimbursementBatchEmailRequest.php create mode 100644 app/Http/Requests/Reimbursements/ReimbursementBatchLockRequest.php create mode 100644 app/Http/Requests/Reimbursements/ReimbursementExportBatchRequest.php create mode 100644 app/Http/Requests/Reimbursements/ReimbursementExportRequest.php create mode 100644 app/Http/Requests/Reimbursements/ReimbursementIndexRequest.php create mode 100644 app/Http/Requests/Reimbursements/ReimbursementMarkDonationRequest.php create mode 100644 app/Http/Requests/Reimbursements/ReimbursementProcessRequest.php create mode 100644 app/Http/Requests/Reimbursements/ReimbursementStoreRequest.php create mode 100644 app/Http/Requests/Reimbursements/ReimbursementUnderProcessingRequest.php create mode 100644 app/Http/Requests/Reimbursements/ReimbursementUpdateRequest.php create mode 100644 app/Http/Requests/Scores/ScoreAddColumnRequest.php create mode 100644 app/Http/Requests/Scores/ScoreClassRequest.php create mode 100644 app/Http/Requests/Scores/ScoreCommentListRequest.php create mode 100644 app/Http/Requests/Scores/ScoreCommentSaveRequest.php create mode 100644 app/Http/Requests/Scores/ScoreCommentUpdateRequest.php create mode 100644 app/Http/Requests/Scores/ScoreLockRequest.php create mode 100644 app/Http/Requests/Scores/ScoreOverviewRequest.php create mode 100644 app/Http/Requests/Scores/ScorePredictorRequest.php create mode 100644 app/Http/Requests/Scores/ScoreStudentRequest.php create mode 100644 app/Http/Requests/Scores/ScoreUpdateRequest.php create mode 100644 app/Http/Requests/Semesters/SchoolYearRangeRequest.php create mode 100644 app/Http/Requests/Semesters/SemesterNormalizeRequest.php create mode 100644 app/Http/Requests/Semesters/SemesterRangeRequest.php create mode 100644 app/Http/Requests/Semesters/SemesterResolveRequest.php create mode 100644 app/Http/Requests/Users/LoginActivityRequest.php create mode 100644 app/Http/Requests/Users/UserListRequest.php create mode 100644 app/Http/Requests/Users/UserStoreRequest.php create mode 100644 app/Http/Requests/Users/UserUpdateRequest.php create mode 100644 app/Http/Resources/Auth/RegisterResource.php create mode 100644 app/Http/Resources/Discounts/DiscountParentResource.php create mode 100644 app/Http/Resources/Discounts/DiscountVoucherResource.php create mode 100644 app/Http/Resources/Expenses/ExpenseResource.php create mode 100644 app/Http/Resources/Expenses/ExpenseStaffResource.php create mode 100644 app/Http/Resources/ExtraCharges/ExtraChargeInvoiceOptionResource.php create mode 100644 app/Http/Resources/ExtraCharges/ExtraChargeParentOptionResource.php create mode 100644 app/Http/Resources/ExtraCharges/ExtraChargeResource.php create mode 100644 app/Http/Resources/Files/FileMetaResource.php create mode 100644 app/Http/Resources/Finance/FinancialDiscountResource.php create mode 100644 app/Http/Resources/Finance/FinancialExpenseSummaryResource.php create mode 100644 app/Http/Resources/Finance/FinancialInvoiceResource.php create mode 100644 app/Http/Resources/Finance/FinancialPaymentResource.php create mode 100644 app/Http/Resources/Finance/FinancialRefundResource.php create mode 100644 app/Http/Resources/Finance/FinancialReimbursementSummaryResource.php create mode 100644 app/Http/Resources/Finance/FinancialReportResource.php create mode 100644 app/Http/Resources/Finance/FinancialSummaryResource.php create mode 100644 app/Http/Resources/Finance/FinancialUnpaidParentResource.php create mode 100644 app/Http/Resources/Grading/BelowSixtyStudentResource.php create mode 100644 app/Http/Resources/Grading/GradingScoreResource.php create mode 100644 app/Http/Resources/Grading/HomeworkTrackingTeacherResource.php create mode 100644 app/Http/Resources/Incidents/IncidentAnalysisStudentResource.php create mode 100644 app/Http/Resources/Incidents/IncidentGradeResource.php create mode 100644 app/Http/Resources/Incidents/IncidentResource.php create mode 100644 app/Http/Resources/Incidents/IncidentStudentOptionResource.php create mode 100644 app/Http/Resources/Invoices/InvoiceManagementParentResource.php create mode 100644 app/Http/Resources/Invoices/InvoiceResource.php create mode 100644 app/Http/Resources/Payments/PaymentNotificationLogResource.php create mode 100644 app/Http/Resources/Payments/PaymentResource.php create mode 100644 app/Http/Resources/Payments/PaymentTransactionResource.php create mode 100644 app/Http/Resources/Payments/PaypalTransactionResource.php create mode 100644 app/Http/Resources/Reimbursements/ReimbursementBatchResource.php create mode 100644 app/Http/Resources/Reimbursements/ReimbursementExpenseResource.php create mode 100644 app/Http/Resources/Reimbursements/ReimbursementRecipientResource.php create mode 100644 app/Http/Resources/Reimbursements/ReimbursementResource.php create mode 100644 app/Http/Resources/Reimbursements/ReimbursementUnderProcessingItemResource.php create mode 100644 app/Http/Resources/Scores/ScoreCommentResource.php create mode 100644 app/Http/Resources/Scores/ScorePredictorStudentResource.php create mode 100644 app/Http/Resources/Scores/ScoreStudentResource.php create mode 100644 app/Http/Resources/Semesters/SemesterRangeResource.php create mode 100644 app/Http/Resources/Semesters/SemesterResolveResource.php create mode 100644 app/Http/Resources/Users/LoginActivityResource.php create mode 100644 app/Http/Resources/Users/UserWithRolesResource.php create mode 100644 app/Interfaces/ScoreCalculatorInterface.php create mode 100644 app/Models/CurrentIncident.php rename app/Models/{Flag.php => Incident.php} (86%) create mode 100644 app/Services/Assignment/AssignmentContextService.php create mode 100644 app/Services/Assignment/AssignmentDataLoaderService.php create mode 100644 app/Services/Assignment/AssignmentMetaService.php create mode 100644 app/Services/Assignment/AssignmentSectionService.php delete mode 100644 app/Services/AttendanceTrackingService.php create mode 100644 app/Services/Auth/RegistrationCaptchaService.php create mode 100644 app/Services/Auth/RegistrationFormatterService.php create mode 100644 app/Services/Auth/RegistrationService.php create mode 100644 app/Services/BroadcastEmail/BroadcastEmailComposerService.php create mode 100644 app/Services/BroadcastEmail/BroadcastEmailDispatchService.php create mode 100644 app/Services/BroadcastEmail/BroadcastEmailImageService.php create mode 100644 app/Services/BroadcastEmail/BroadcastEmailRecipientService.php create mode 100644 app/Services/BroadcastEmail/BroadcastEmailSenderOptionsService.php create mode 100644 app/Services/ClassPrep/ClassRosterService.php create mode 100644 app/Services/ClassPrep/StickerCountService.php create mode 100644 app/Services/ClassPreparation/ClassPreparationAdjustmentService.php create mode 100644 app/Services/ClassPreparation/ClassPreparationCalculatorService.php create mode 100644 app/Services/ClassPreparation/ClassPreparationContextService.php create mode 100644 app/Services/ClassPreparation/ClassPreparationInventoryService.php create mode 100644 app/Services/ClassPreparation/ClassPreparationLogService.php create mode 100644 app/Services/ClassPreparation/ClassPreparationRosterService.php create mode 100644 app/Services/ClassPreparation/ClassPreparationService.php create mode 100644 app/Services/Communication/CommunicationFamilyService.php create mode 100644 app/Services/Communication/CommunicationLogService.php create mode 100644 app/Services/Communication/CommunicationPreviewService.php create mode 100644 app/Services/Communication/CommunicationSendService.php create mode 100644 app/Services/Communication/CommunicationStudentService.php create mode 100644 app/Services/Communication/CommunicationTemplateService.php create mode 100644 app/Services/CompetitionScores/CompetitionScoresContextService.php create mode 100644 app/Services/CompetitionScores/CompetitionScoresQueryService.php create mode 100644 app/Services/CompetitionScores/CompetitionScoresRosterService.php create mode 100644 app/Services/CompetitionScores/CompetitionScoresSaveService.php create mode 100644 app/Services/Discounts/DiscountApplyService.php create mode 100644 app/Services/Discounts/DiscountContextService.php create mode 100644 app/Services/Discounts/DiscountInvoiceService.php create mode 100644 app/Services/Discounts/DiscountParentService.php create mode 100644 app/Services/Discounts/DiscountVoucherService.php create mode 100644 app/Services/EmailService.php create mode 100644 app/Services/Expenses/ExpenseContextService.php create mode 100644 app/Services/Expenses/ExpenseQueryService.php create mode 100644 app/Services/Expenses/ExpenseReceiptService.php create mode 100644 app/Services/Expenses/ExpenseRetailorService.php create mode 100644 app/Services/Expenses/ExpenseStaffService.php create mode 100644 app/Services/Expenses/ExpenseStatusService.php create mode 100644 app/Services/Expenses/ExpenseWriteService.php create mode 100644 app/Services/ExtraCharges/ExtraChargesChargeService.php create mode 100644 app/Services/ExtraCharges/ExtraChargesContextService.php create mode 100644 app/Services/ExtraCharges/ExtraChargesInvoiceService.php create mode 100644 app/Services/ExtraCharges/ExtraChargesListService.php create mode 100644 app/Services/ExtraCharges/ExtraChargesMetaService.php create mode 100644 app/Services/ExtraCharges/ExtraChargesParentService.php create mode 100644 app/Services/Files/ExamDraftDownloadNameService.php create mode 100644 app/Services/Files/FileServeService.php create mode 100644 app/Services/Finance/FinancialChartService.php create mode 100644 app/Services/Finance/FinancialDonationRecipientService.php create mode 100644 app/Services/Finance/FinancialPaymentService.php create mode 100644 app/Services/Finance/FinancialPdfReportService.php create mode 100644 app/Services/Finance/FinancialReportService.php create mode 100644 app/Services/Finance/FinancialSchoolYearService.php create mode 100644 app/Services/Finance/FinancialSummaryService.php create mode 100644 app/Services/Finance/FinancialUnpaidParentsService.php create mode 100644 app/Services/Grading/GradingBelowSixtyService.php create mode 100644 app/Services/Grading/GradingLockService.php create mode 100644 app/Services/Grading/GradingOverviewService.php create mode 100644 app/Services/Grading/GradingPlacementService.php create mode 100644 app/Services/Grading/GradingRefreshService.php create mode 100644 app/Services/Grading/GradingScoreService.php create mode 100644 app/Services/Grading/HomeworkTrackingCalendarService.php create mode 100644 app/Services/Grading/HomeworkTrackingService.php create mode 100644 app/Services/Incidents/CurrentIncidentService.php create mode 100644 app/Services/Incidents/IncidentAnalysisService.php create mode 100644 app/Services/Incidents/IncidentHistoryService.php create mode 100644 app/Services/Incidents/IncidentLookupService.php create mode 100644 app/Services/Invoices/InvoiceConfigService.php create mode 100644 app/Services/Invoices/InvoiceGenerationService.php create mode 100644 app/Services/Invoices/InvoiceGradeService.php create mode 100644 app/Services/Invoices/InvoiceManagementService.php create mode 100644 app/Services/Invoices/InvoicePaymentService.php create mode 100644 app/Services/Invoices/InvoicePdfService.php create mode 100644 app/Services/Invoices/InvoiceTuitionService.php create mode 100644 app/Services/Payments/PaymentBalanceService.php create mode 100644 app/Services/Payments/PaymentEnrollmentEventService.php create mode 100644 app/Services/Payments/PaymentEventChargesService.php create mode 100644 app/Services/Payments/PaymentLookupService.php create mode 100644 app/Services/Payments/PaymentManualService.php create mode 100644 app/Services/Payments/PaymentNotificationDispatchService.php create mode 100644 app/Services/Payments/PaymentNotificationService.php create mode 100644 app/Services/Payments/PaymentPlanService.php create mode 100644 app/Services/Payments/PaymentTransactionService.php create mode 100644 app/Services/Payments/PaypalPaymentService.php create mode 100644 app/Services/Payments/PaypalTransactionsService.php create mode 100644 app/Services/PhoneFormatterService.php create mode 100644 app/Services/Reimbursements/ReimbursementBatchAssignmentService.php create mode 100644 app/Services/Reimbursements/ReimbursementBatchService.php create mode 100644 app/Services/Reimbursements/ReimbursementContextService.php create mode 100644 app/Services/Reimbursements/ReimbursementCrudService.php create mode 100644 app/Services/Reimbursements/ReimbursementDonationService.php create mode 100644 app/Services/Reimbursements/ReimbursementEmailService.php create mode 100644 app/Services/Reimbursements/ReimbursementExportService.php create mode 100644 app/Services/Reimbursements/ReimbursementFileService.php create mode 100644 app/Services/Reimbursements/ReimbursementLookupService.php create mode 100644 app/Services/Reimbursements/ReimbursementQueryService.php create mode 100644 app/Services/Reimbursements/ReimbursementRecipientService.php create mode 100644 app/Services/SchoolIdService.php create mode 100644 app/Services/Scores/AttendanceCalculator.php create mode 100644 app/Services/Scores/ExamScoreService.php create mode 100644 app/Services/Scores/HomeworkCalculator.php create mode 100644 app/Services/Scores/HomeworkScoreService.php create mode 100644 app/Services/Scores/ParticipationScoreService.php create mode 100644 app/Services/Scores/ProjectCalculator.php create mode 100644 app/Services/Scores/ProjectScoreService.php create mode 100644 app/Services/Scores/QuizCalculator.php create mode 100644 app/Services/Scores/QuizScoreService.php create mode 100644 app/Services/Scores/ScoreCommentService.php create mode 100644 app/Services/Scores/ScoreDashboardService.php create mode 100644 app/Services/Scores/ScorePredictorDataService.php create mode 100644 app/Services/Scores/ScorePredictorRiskService.php create mode 100644 app/Services/Scores/ScorePredictorService.php create mode 100644 app/Services/Scores/ScoreTermService.php create mode 100644 app/Services/Scores/SemesterScoreService.php create mode 100644 app/Services/SemesterRangeService.php create mode 100644 app/Services/Semesters/SemesterConfigService.php create mode 100644 app/Services/Users/LoginActivityService.php create mode 100644 app/Services/Users/UserEventService.php create mode 100644 app/Services/Users/UserListService.php create mode 100644 app/Services/Users/UserManagementService.php create mode 100644 config/sanctum.php create mode 100644 database/migrations/2026_02_23_204035_create_current_incident_table.php rename database/migrations/{2026_02_23_204036_create_flag_table.php => 2026_02_23_204036_create_incident_table.php} (67%) create mode 100644 database/migrations/2026_02_23_900056_create_communication_logs_table.php create mode 100644 database/migrations/2026_03_09_050312_create_personal_access_tokens_table.php create mode 100644 database/migrations/2026_03_09_064908_create_sessions_table.php create mode 100644 school_api create mode 100644 storage/testing/files/cached.pdf create mode 100644 storage/testing/files/sample.exe create mode 100644 storage/testing/files/sample.pdf create mode 100644 tests/Feature/Api/V1/Auth/RegisterControllerTest.php create mode 100644 tests/Feature/Api/V1/Auth/RegisterPayloadDebugTest.php create mode 100644 tests/Feature/Api/V1/BroadcastEmail/BroadcastEmailControllerTest.php create mode 100644 tests/Feature/Api/V1/ClassPrep/ClassPrepControllerTest.php create mode 100644 tests/Feature/Api/V1/ClassPreparation/ClassPreparationControllerTest.php create mode 100644 tests/Feature/Api/V1/Communication/CommunicationControllerTest.php create mode 100644 tests/Feature/Api/V1/CompetitionScores/CompetitionScoresControllerTest.php create mode 100644 tests/Feature/Api/V1/Discounts/DiscountControllerTest.php create mode 100644 tests/Feature/Api/V1/Expenses/ExpenseControllerTest.php create mode 100644 tests/Feature/Api/V1/ExtraCharges/ExtraChargesControllerTest.php create mode 100644 tests/Feature/Api/V1/Finance/FinancialControllerTest.php create mode 100644 tests/Feature/Api/V1/Finance/InvoiceControllerTest.php create mode 100644 tests/Feature/Api/V1/Finance/PaymentControllerTest.php create mode 100644 tests/Feature/Api/V1/Finance/PaymentEventChargesControllerTest.php create mode 100644 tests/Feature/Api/V1/Finance/PaymentManualControllerTest.php create mode 100644 tests/Feature/Api/V1/Finance/PaymentNotificationControllerTest.php create mode 100644 tests/Feature/Api/V1/Finance/PaymentTransactionControllerTest.php create mode 100644 tests/Feature/Api/V1/Finance/PaypalTransactionsControllerTest.php create mode 100644 tests/Feature/Api/V1/Finance/ReimbursementControllerTest.php create mode 100644 tests/Feature/Api/V1/Grading/GradingControllerTest.php create mode 100644 tests/Feature/Api/V1/Grading/HomeworkTrackingControllerTest.php create mode 100644 tests/Feature/Api/V1/Incidents/IncidentControllerTest.php create mode 100644 tests/Feature/Api/V1/Reports/FilesControllerTest.php create mode 100644 tests/Feature/Api/V1/Scores/FinalControllerTest.php create mode 100644 tests/Feature/Api/V1/Scores/HomeworkControllerTest.php create mode 100644 tests/Feature/Api/V1/Scores/MidtermControllerTest.php create mode 100644 tests/Feature/Api/V1/Scores/ParticipationControllerTest.php create mode 100644 tests/Feature/Api/V1/Scores/ProjectControllerTest.php create mode 100644 tests/Feature/Api/V1/Scores/QuizControllerTest.php create mode 100644 tests/Feature/Api/V1/Scores/ScoreCommentControllerTest.php create mode 100644 tests/Feature/Api/V1/Scores/ScoreControllerTest.php create mode 100644 tests/Feature/Api/V1/Scores/ScorePredictorControllerTest.php create mode 100644 tests/Feature/Api/V1/System/SemesterRangeControllerTest.php create mode 100644 tests/Feature/Api/V1/Users/UserControllerTest.php create mode 100644 tests/Unit/Services/Administrator/AdminNotificationUserServiceTest.php create mode 100644 tests/Unit/Services/Administrator/AdministratorAbsenceServiceTest.php create mode 100644 tests/Unit/Services/Administrator/AdministratorDashboardServiceTest.php create mode 100644 tests/Unit/Services/Administrator/AdministratorEnrollmentEventServiceTest.php create mode 100644 tests/Unit/Services/Administrator/AdministratorEnrollmentRefundServiceTest.php create mode 100644 tests/Unit/Services/Administrator/AdministratorEnrollmentServiceTest.php create mode 100644 tests/Unit/Services/Administrator/AdministratorMetricsServiceTest.php create mode 100644 tests/Unit/Services/Administrator/AdministratorNotificationServiceTest.php create mode 100644 tests/Unit/Services/Administrator/AdministratorSharedServiceTest.php create mode 100644 tests/Unit/Services/Administrator/AdministratorTeacherSubmissionServiceTest.php create mode 100644 tests/Unit/Services/Administrator/AdministratorUserSearchServiceTest.php create mode 100644 tests/Unit/Services/Assignment/AssignmentContextServiceTest.php create mode 100644 tests/Unit/Services/Assignment/AssignmentDataLoaderServiceTest.php create mode 100644 tests/Unit/Services/Assignment/AssignmentMetaServiceTest.php create mode 100644 tests/Unit/Services/Assignment/AssignmentSectionServiceTest.php create mode 100644 tests/Unit/Services/Attendance/AttendanceQueryServiceTest.php create mode 100644 tests/Unit/Services/Attendance/AttendanceSemesterRangeServiceTest.php create mode 100644 tests/Unit/Services/Attendance/AttendanceServiceTest.php create mode 100644 tests/Unit/Services/Attendance/StaffAttendanceServiceTest.php create mode 100644 tests/Unit/Services/Auth/RegistrationCaptchaServiceTest.php create mode 100644 tests/Unit/Services/Auth/RegistrationFormatterServiceTest.php create mode 100644 tests/Unit/Services/Auth/RegistrationServiceTest.php create mode 100644 tests/Unit/Services/BroadcastEmail/BroadcastEmailComposerServiceTest.php create mode 100644 tests/Unit/Services/BroadcastEmail/BroadcastEmailRecipientServiceTest.php create mode 100644 tests/Unit/Services/BroadcastEmail/BroadcastEmailSenderOptionsServiceTest.php create mode 100644 tests/Unit/Services/ClassPrep/ClassRosterServiceTest.php create mode 100644 tests/Unit/Services/ClassPrep/StickerCountServiceTest.php create mode 100644 tests/Unit/Services/ClassPreparation/ClassPreparationAdjustmentServiceTest.php create mode 100644 tests/Unit/Services/ClassPreparation/ClassPreparationCalculatorServiceTest.php create mode 100644 tests/Unit/Services/ClassPreparation/ClassPreparationContextServiceTest.php create mode 100644 tests/Unit/Services/ClassPreparation/ClassPreparationInventoryServiceTest.php create mode 100644 tests/Unit/Services/ClassPreparation/ClassPreparationLogServiceTest.php create mode 100644 tests/Unit/Services/ClassPreparation/ClassPreparationRosterServiceTest.php create mode 100644 tests/Unit/Services/ClassPreparation/ClassPreparationServiceTest.php create mode 100644 tests/Unit/Services/Communication/CommunicationTemplateServiceTest.php create mode 100644 tests/Unit/Services/CompetitionScores/CompetitionScoresSaveServiceTest.php create mode 100644 tests/Unit/Services/Discounts/DiscountApplyServiceTest.php create mode 100644 tests/Unit/Services/Expenses/ExpenseReceiptServiceTest.php create mode 100644 tests/Unit/Services/Expenses/ExpenseStaffServiceTest.php create mode 100644 tests/Unit/Services/ExtraCharges/ExtraChargesMetaServiceTest.php create mode 100644 tests/Unit/Services/Files/ExamDraftDownloadNameServiceTest.php create mode 100644 tests/Unit/Services/Files/FileServeServiceTest.php create mode 100644 tests/Unit/Services/Finance/FinancialChartServiceTest.php create mode 100644 tests/Unit/Services/Finance/FinancialPaymentServiceTest.php create mode 100644 tests/Unit/Services/Finance/FinancialPdfReportServiceTest.php create mode 100644 tests/Unit/Services/Finance/FinancialReportServiceTest.php create mode 100644 tests/Unit/Services/Finance/FinancialSchoolYearServiceTest.php create mode 100644 tests/Unit/Services/Finance/FinancialSummaryServiceTest.php create mode 100644 tests/Unit/Services/Finance/FinancialUnpaidParentsServiceTest.php create mode 100644 tests/Unit/Services/Grading/GradingBelowSixtyServiceTest.php create mode 100644 tests/Unit/Services/Grading/GradingLockServiceTest.php create mode 100644 tests/Unit/Services/Grading/HomeworkTrackingServiceTest.php create mode 100644 tests/Unit/Services/Incidents/IncidentAnalysisServiceTest.php create mode 100644 tests/Unit/Services/Incidents/IncidentHistoryServiceTest.php create mode 100644 tests/Unit/Services/Incidents/IncidentLookupServiceTest.php create mode 100644 tests/Unit/Services/Invoices/InvoiceConfigServiceTest.php create mode 100644 tests/Unit/Services/Invoices/InvoiceGenerationServiceTest.php create mode 100644 tests/Unit/Services/Invoices/InvoiceGradeServiceTest.php create mode 100644 tests/Unit/Services/Invoices/InvoiceManagementServiceTest.php create mode 100644 tests/Unit/Services/Invoices/InvoicePaymentServiceTest.php create mode 100644 tests/Unit/Services/Invoices/InvoicePdfServiceTest.php create mode 100644 tests/Unit/Services/Invoices/InvoiceTuitionServiceTest.php create mode 100644 tests/Unit/Services/Payments/PaymentBalanceServiceTest.php create mode 100644 tests/Unit/Services/Payments/PaymentEnrollmentEventServiceTest.php create mode 100644 tests/Unit/Services/Payments/PaymentEventChargesServiceTest.php create mode 100644 tests/Unit/Services/Payments/PaymentLookupServiceTest.php create mode 100644 tests/Unit/Services/Payments/PaymentManualServiceTest.php create mode 100644 tests/Unit/Services/Payments/PaymentNotificationDispatchServiceTest.php create mode 100644 tests/Unit/Services/Payments/PaymentNotificationServiceTest.php create mode 100644 tests/Unit/Services/Payments/PaymentPlanServiceTest.php create mode 100644 tests/Unit/Services/Payments/PaymentTransactionServiceTest.php create mode 100644 tests/Unit/Services/Payments/PaypalPaymentServiceTest.php create mode 100644 tests/Unit/Services/Payments/PaypalTransactionsServiceTest.php create mode 100644 tests/Unit/Services/Reimbursements/ReimbursementBatchAssignmentServiceTest.php create mode 100644 tests/Unit/Services/Reimbursements/ReimbursementBatchServiceTest.php create mode 100644 tests/Unit/Services/Reimbursements/ReimbursementDonationServiceTest.php create mode 100644 tests/Unit/Services/Reimbursements/ReimbursementRecipientServiceTest.php create mode 100644 tests/Unit/Services/Scores/ExamScoreServiceTest.php create mode 100644 tests/Unit/Services/Scores/HomeworkScoreServiceTest.php create mode 100644 tests/Unit/Services/Scores/ParticipationScoreServiceTest.php create mode 100644 tests/Unit/Services/Scores/ProjectScoreServiceTest.php create mode 100644 tests/Unit/Services/Scores/QuizScoreServiceTest.php create mode 100644 tests/Unit/Services/Scores/ScoreCommentServiceTest.php create mode 100644 tests/Unit/Services/Scores/ScoreDashboardServiceTest.php create mode 100644 tests/Unit/Services/Scores/ScorePredictorServiceTest.php create mode 100644 tests/Unit/Services/Scores/ScoreTermServiceTest.php create mode 100644 tests/Unit/Services/Scores/SemesterScoreServiceTest.php create mode 100644 tests/Unit/Services/Semesters/SemesterConfigServiceTest.php create mode 100644 tests/Unit/Services/Semesters/SemesterRangeServiceTest.php create mode 100644 tests/Unit/Services/Users/LoginActivityServiceTest.php create mode 100644 tests/Unit/Services/Users/UserEventServiceTest.php create mode 100644 tests/Unit/Services/Users/UserListServiceTest.php create mode 100644 tests/Unit/Services/Users/UserManagementServiceTest.php diff --git a/app/Config/Services.php b/app/Config/Services.php index f98dc979..668e31bd 100755 --- a/app/Config/Services.php +++ b/app/Config/Services.php @@ -3,7 +3,7 @@ namespace Config; use App\Services\EmailService; -use App\Services\SemesterScoreService; +use App\Services\Scores\SemesterScoreService; class Services { diff --git a/app/Http/Controllers/Api/Api.zip b/app/Http/Controllers/Api/Api.zip deleted file mode 100644 index fbcb139079f3e5f6587d4df163537ba7fddda706..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 240011 zcmb5VQ;=wFlOD4FCvo1`Gi3 zZ->G^=_vmsKmhc^n(4@EN5jg(0RT{N0RUkCN4k=!xuvsvNNfX#xkKLNq^>Q={Xri&igqlr2BAaoMx8gY3#$Roz+{9ufcOt5C+0Z#<+;WewAWJ(kMXqO)j z(&2vB)L<}Qwa{+D6W^T#8;|}T0ZJcDUEQgph~CIK&?%?n<-T3FCr&uJALs!QOtdJ% zevZTOSJsFkelwPMCZwY>iDb^nkowvXp8^jzqrlD~!(C=x*tbyaiCIU+=seO>JHTK8 z&FH17jtEloQTt&i$D~63R1l0>(*}wN-!aJpvx&{>%qcF2za-Y_@ML5>)n@1m&( zaM6dDH|QqRBkVThPui^{YEYjntv!mifH&l4VX(z!zzdVFe&yZ8O);-h188i=G=f0P zgfTAV52BHDC-1J5{tmSPm zrBl9w8|<7jqNsr*I--p)7?c|}>`2m8w~pN(*dsL>%U=&MCp*Df;(=T+uXm(&YOHc( zN9slx!)_25=;Ob*KbFrK1P|lnZS!JQbIUy7Urdt}4(O5gwL*L`=EfSTVdlfGK1`Hm za>$%zJ(mOLmw6%ruN*WI*qmnoXhe98tnG2($y?cnjB$LcYhBHNNtwP7EhjXYgwmqm_zviC?K@`5grODY}}AR!4CwWVmX`-ADIPI)xhXz0K`4XZ8gOgm?@M;y zpJ=zg^P9iE8Q1ZCTh{aJ3YKm84fJoypJl@4OVvyPOEwF?c`yW1?*;`BHKqcSzi!+S zTiT~c;UNp±F5Vz9YIcb~8*pcx2P=E@9dd5a_MQZod}fleDZqXhzk38+Hab~AJM zW<%eF+%_=qD$l&UDMozEBg4J$h78!ju82w8RLSKK!aVyM3v6c&zvvTHM?lT_S}p)~ z<`hoS2dW!r&tUHhB|Yo)tu0kF;aeT^P$*qoxC&P3r&mql{B?Rwv+z$%{(}6w@H8(k z;t5NXQzKr!i()1OA}xNg!K6e!!x&1HRD|YM z!x&Rzkm7so5wO;yTX==4`fXHIB7y>+AgSYZn}RH$n&vMR6D>FguBGOd?;_BPk=Cg$ z)X-+~oyio?s_HZW%TPG|r3Ssq6efXt-FsjR86ho;NJuz{U1pr{-#{Em?$H!UsL*OK z4pBUv54H<+%co@Pw}WZj(<-FZ?BzS9=36V^zV)Igi*Xl6MUO@-rMhv(u`(aI1y*%f zJv&7_!h*+i2sK=3JC)7Xmd?v{)4BbqaTHa`rH&=|1g!x&4VedPxmY&%ILv{$fM56&j87OmH)WQBn?M8_h(1&-9wY2ur_6L}XF#re*zY3)YLg4K&kgn;c z$zs+I8=T&e1&+y_#$$B0dYkG033O=F7!{?0IB5VO{$eE2JJWJL)2bP?$h|t>tWvk9 zlLH5245Yy~uLd7P)00XKC6 zP4*8_z=g#Un<>wX?Ivdhj=F%WjI}p$Y7Cf)bX6^C<($+7vxJc%dS$2-dUL7@JrADh$Ky3)#M4rLz%(BNqUyGv-?vWf} zKSCc(a;nx`#4Wmd@Z*o_?eyD8_wP?PKi}=^?#pVK%{9tgC6_edWZ2x`RK{<`Mg5Zl zOLD~>4<{I#!tw*#I<~lija|>5R0W!kxX8fSJBqi&7MnZcn~A5S*qTDaUA5mdCZtt_e4 zc4ZxmRp~D4aAPwXk*nGs$qAEa1#Qlla{XA9?4Z`~>{|G07Eg}{BShsYE%9SRbFW11 z92sTzz_|TFpntjsI>$6QwTy@n#rfY3?vwCot{YI&^`TsbaKzKA%@GwK;YqjH*Qdzz;wl4yOR_j$Jx}cL z=!aBY7%PDk&emq+4D;o_Nlvq?=N^4zQ!UvN>l*}FG8=tirDFuouOvFzHm-nGw#_L% zrZ5c}p+lw|9yrlBn*@VdH_mj&^}c^;T_#?{RhEgCAotetCUgoSt0W0bi3krUl!r)9bb{d%*)^ zp*{-*Qrk~#Cz=Ms*`HZ*lo9S|m}BvLQG%ZZI7wS-1Z@;wg#4(>5iUssu@H24GphsI zpE*|fAsGHq3BnG!Fh>j^)4Sk!ZURr`j7E;>>OO0{4s?Lcr0SNebsIJ85+WaOqIOXq zF%nXmDJd~xD9`tE0BdgU%*cyYtZ59v6E}HcC4xmp~%q7G;H8nU$!GyG;`LgVRUJcHC~grK-0RaYSmV-*XZL6M*@4=0V?QZnYLrq^=t?kt4lA!g z*g)&+EXx7{inP;FMpZQ~vDI>?p>;`DSz6p$A@xF6FjaolFfEo*iSd2W)uZvX=AfbM za&GCR)tbu17EuV&R@s+9Jp~j**WXqZol+M$oM)i}ZAUog12cZx@JqlqNRVxwZew>_ z!LCy7Ln&%l`V1=*L_1Wsr5tfh`#v?Y`iQ1cvxe`8Q%k+7L~8ireE}-ZQ*c@^*$d0& zrc$;qX){c;vZ6(Ys0#%O+Q(9tFgYubA(Ju`tKxlYg{VzrRdpB>`=!_+SYimAtDF|E zPCwuFjr_v$0;+xtvP7}#dyGdD-2f|<)&Jc{Tq88~de4+$9Zo9*0eG!Nh&nMCw_;WN z+=~UThCL&7C5Rf!o+*?%)}sa=)lYzEmgBcaFZXxn$b(BXJ;Y4&?^ObK?#$)Dadz%Z za#LGbF#PIigxl3$Zd^7x2uGI7npxv~*RSF(DGz!aSP`QNU4O^ar(3rt=%?wXPioGHg)I)wTa)g9gfx_5-5 z$D}Zvy%;>iQgsi$^iD6GZPC4liZ(=(f=rr3aM45DKiRX8P{0OUgb3^#l?=EsCzP3P z?xfnbS1evzqMW6RQ8zapKpWkgyf@>UaOu)Zhwivsv_92IJsGtb2a-PW3HZq|x#_|P zUDiQI<4L-$68(-4MWSe4tNy_S5OMELWR_n7zs#vpt!fge5S1(>zsjO3AfU$faIuVo zo)y}zqpV&$^*3@&%#!ib(+2gqXuIAFpk~%f0Dr1@Zobg}YlnJqG5|*RPxHD83jl!i zA04W)sk5`Cz1{zPgL6!2hI&+*2m=n%2{Zet#3We`Njgsiao^~G ziOW&BgQKll1*hIK8Z>Zs^YSs4cWe60hlxmNj^yP$iKJqRFMefG6RB>%WIQQjSP8sO zZowmp6hoe;KW#Wj;DcdMhyd9%8$8iIX;?W>3ZnWJEof+=D6o!{927OSRbmK1fkXerW~spxZEc30Zo?wqt?KyIzH6ufUniD# z^%fr7{Fnkh<1b4dI8~M|?s4)Z>_@PB;O=8*@y^{zC6P=!!M?A6vE&<+xSMK*T-~N8 z{u8+0>gU;x%5X#yCk7a0{8diE_HYcFdR_K9d)MG)9s=54M6JY0$0aEfge(|Ll@Ie$ zc1UXPM(N(yGt7DzI+Uxia;>?9YrJwJk+KOFyL7RI?dmN1k37XNlMaFT0lmjVz%5ab zRf%2(&>nVwEd&pFoGgcYyY58flP~o88L5axv*DUuth6tAA=jty$a8^w*Ti;19b+s& zoebMvV)X$Szyqvhi&yV2;pK6E52V=KLM^%rul?DzQcGigYE|K-6-4Sv5g7D8KT0@^ z3|FNYL-W55w^tNMK)r{E18ONrKVB?t9U@Ja0&FC5&Y!I<93@Ij}q*IcKBezCM2F3T(6t#jtBsgm)TpNR-WGQ<$;MIu` z%z#ZUh2&!u#iM>p&Q&V$v9pfPm6g+Ic(H;W6cU`2Y2WZGcM!!%NqF|Jv-kk6HiQi_ zBPXzPP-Y}OLU+y4R<4D!D1QnP*sm22nw#Hv<)Qbt`R zGNZ_r9(bykyW^xh(N<{(!%;PQ^FGPH$Ro8YBB%8fMU&D`m3oD)^e;oad_-kh`8)#d-5h$Cv7b{hf+zOU*wq$JpoQ6ExRR8kg&vV}m& zm9&X1E#PaT)iphH1!TWF_n@0=AW3XW-$rY^&L@rLZeJg=?NG46i>zY_odTVP9QYKd zGv0veM0dUjPCKyc-BrsMQw+^KL7vj2d))eblXvZ{29xh7B_@Dv>sUZFN!Nw9m#l{> zf`cD|U_L?nAUZ-p8Fs7D0rrU|u~q%5Fh1xOH;SPyk2rSie5pxSpX0zp4pjl(Nh(6D ze%K)S3cq)m(r8&-h|HQn6Os(TRXb!O0LI0`EM6cKv>d%jx>Y+@Un0%Vyuu>!lr5J= zA!MAl2&615FKwy^j5|BCCEKmx`5%Xw*E&;Vy51aksWSeG1DN_;h$DiZ^xNYW9A39; z!IJ)582WlC5L>A2>~M!r!(kublwQGpx5dmj>)+$5uZI7>>U7Mn^Ocf-BTg22GfK^Mo^?5h<-h%#tNWz4CW*#yiSc zQCr*Bsv{Gie$AYC#MkB)YcydB>P8kN(;5ul`5PV@2tl?*j}s`06JPBlNk*OojtTbg zDwmOz$sFe-Ky^8+bo-4}P=}y@F%MG(L-LiA=p2Fm+UNt*GEs`D`$)<2z>0u^@cS(= zdu!f#Y!h_lY+eW2`n#DG-%R1fkPq2^MA@D0zDY+9xsUbiyPaKZXX0m6Bry(+33vPO&}5LuS*-%#9D@&nTYC} zZYtW#{sZuzJhTGU(2-t{>bLrbhe-cSf6@PghyR-Q{txW);L5c`*ZAM&v@L47_KOTC zzPEJ@N`E-YQ4fWAP$;qxAm-*uZR^9$Dz}bG8B1Cla#IOPU>2-d&gj; zl6_bW@0B&xsOd67vG#EM)C{f$CZnY&D{LPE|Ih#@`xukXW~9PSt^aLO20ai`;|hg# z1Lk`7aXshR(qfrCbJSuoj__XVkl0HHO~h=}#@d2~fK#QF%1i1-syJ3kP@?N7h|M=C zzm&XsT|u*hu@4G9f?en0&a7U8FBq`kF*TJuM9j3k-ik07@xbgj&!)^2wPRmR5G}sD z@M^X%NSM>Qpt-=2NMzn97@MmH6Rco%sj>jg6aa2&E&aJ|`7y z*)Et>dCX$cV2D$D@$Bg7;+ymN)7a-5 z&6uU?zD}_w$J*FZ{aqZdnwsroxmz(&%(@g5T^ju!Io&=sj`(03VIfIJZO~N2VP*1- z9my8$7sd)Vkt--aM7>t-Wh~PFsagChDDyyO2sXSWxml~AGUWjN1AnzMwv13v{K!Hi zukEm1oI6-_ndrJ8(yHE><`4I*B{QM>%`(|VRSq95m+7HE7Kly5oA@-3)H{ztrg8HA zyDhnMYidN6Yde0*>NgSI)Ob`jYLlg7|HuB+?Fb*m9e?uQ348(u+pH6w%pbhzY|Imh6+ob~Ht>fv)1yDEEbL#RJ}&x(w;1$>}A2JI(H zat+hXT~lEtbXp-~>7i-XtrURVvpw22+H-Y-xJ0bK*WO-zc|Ale!&iww!KBpQ_?&P? ztstA_4f!-bX%$0r0}jU4Gooh;hOc~6CxJv$6;9|C74^l3Btcy>gzg003%( z{uk=1I{zOfmj6Mw+2*x&-)v3VeWYHiZA(&Gmvh>*i$~(VM&lGXaE<11l;1PpT$I{@JIz|u8yvn@SHO5Nx%5fn_*<)N9j zK4O@a;)(T_hMo?t@$A0GyK3B1W-e1A%lKFl?htf!c{Te#~urK1SrRRx+=hzLX(La zC_4JpuM54Od~z{KR+^2t-@sb5xoMZ+{7qX@Qac znf`!Ub#(SlYtn)NbtR~peX*2K{Rm6Po~*jMBMa)d0zU#18@v#YPMj7Kq2Yu#q5@&Cff1ztvmA#~p?<(Wbm8UInxgYx)4W2^Kkf z&7IpdLP4W=L&ocWM4_D>y*c1VmTOS?<8(y-su=W9jRQ-byU=@^wf$K9OJ2yp%^mV}e=v``-f*FK zl#ve<0f_+_m1;PYcjTxNm%<1x)*(t2UK)(IsOxXjjOSBHe4b0%@;$#fav)eBulgSS zQg9Il)nm(vHFevYwpV1gPxpn(z#yDNQ-#<+E^5zdO-`xgQK;bBtlYY?gjQ4^ryEzH zRzzt&e4P$MTAvu|HK7j8)h( z;q2rm2t^YPwU%3hmjRE3S&EY+QB2dC+42C>aMm+s`?@({O{aGX=mDtc0LEM`h06Ft zS6-YMKNRLo@dycVc)mJe_+s$*JPa~2wk^Tad!``sK)*eZ?52)6y24kRF|Yn}?Ymk- zPK!7g_^>%yK02g`?Y)TTy6J+qp-q?)GK_%W6B_HzuAv!UvC92orceWsq2pL6B8#(G zWkrM8dwpV=yq)mh;%FWaQc0=FMqat4_axGSKDS<2?U01mK0^VDN{yhC{EhGh!y^&c zVAGOTgn*NeEb1|vvKZoLHzIcn+YlG9GQ<;FHQ^?ZYt+++bi{MeR`?EbZKhxu9wnH#B~pp5uBe# z0c>V^s4g!?&8>5fR3_jbh}E$L>f#mD8t0#8Q-IfSJzAGnpxWAtPgZug4v=0QSV7v{ zf5)J^H-#aL@dke_gXAf3f(b4OMbzLsvAhfQR}CF$)BRRRs0^&hh~8Qv=eebXbbc7) zkNZ)FQG-PfH1WtpCHT}`vrPTW!JM1vc9mB*y)am(F&(P)Ex-FC$evw0k=5FeScEa1 zrJe2_i!;ra3s^$$I;tfoc6GV--u9Y>2sG)-l1X~5v738YPJ&T#N+(TAjv4qs7wdEM zz`vuLtRW;FW5Lumnrk4UCCkkBpL}=E&{v+4NhvJb1D0_!)niii13rXNG)e*gvI%er z0K_M97e_p|1Ylx(b6#__?cC@Ya$wrWiqX%`#F2~J&y14W!|BP&{Uzs*lP5b&PHrQ| z%chU8gqpxvdHas?gt~$X1LP;|SJA`=dtPMnofY`|mFPhztcHYvR{~{$83u;LN}JM3 z=Iz16$AQgnA17znp?`9Y8>LYX`e+HKteZ6iS(F%*!^C=X6b%Y*X(g!z<~$X+@#y*Z zV)Jlt-Nin;4YS#gz(VVN zg;L|VpB7*tU2zIr+Y)d@i5zl6v1fF=+>7#Qz7kTCP1TMnNIe2XK>RM$-eSnQEW484 zXKcBSyA;L1U2_#ivRkU&63l)D6AHatb6GhN)R3;E=3_gs7X6pA0)@j?O69Rz1eSP3 zk!&uAG-KqM0Nh14j1hYV^ZWH_1}CHHPf!p(a0 zWS@$za2nXzV>qF$Scgby(x%9W(rdx?kbE`d!b01CaPT6WsPSe$pV!@^Mn?Hq9PI=Y zSgNVXV*CsFhGAsy@UMX9(k1H@<|g~JdliTcK&mO3JKkL&(us2k&rL~?FIIuYvp2sz zeVh5fq$11Ul+(5WK*+q}yals!?Qo3?h%bXrV>MuPSbu$*7}$`O=kuRf9(6*x;4=A+ z?>78b2mWJ9@m}ksX>PMzJO!~O`~weNX7C-y6rVt*g!?7NB-0=*aU_HbhKpjb3)d-a z3S-I+Xj6_u%b|!%a*mV%(IjC^4bPxpyNquI(uJ{ra;0;RWrjrt>aHWgr3Pl>{%T(o zd$G}3!HJ>rsVY2{m?pn0F-)Zrs}#mm@?rMba$IN535AqdMX<^ryMq+^ZDBh_cJ(-y zc)NBUSUecf-oo9TB_v}l+EjNbf-l@CrIF> zbv0eUs5E0MjOmHfQZ^w0xYZMvvr5H5)yR?rzJ2J62piC*^bWXi%Ccq%WK9BY2qBzu zdTLY|H2!XPK2A(OpI!Ion!!Fp40JYJ-QLc3H&@4-$;JLL{^3u`>t$pkBx`@oc5)w_73uwg{yP7|E@+tZz->|59t* z$g21ek57vNa=O(=LTN9L_m*VPQ=d+=vyxlkRG_Hu3}qHOlpt(jvdK^6O=i+acUD6( z%M;oR;fe;+7$VwC-&avvPqjttG<8ANtzA5p4nAqhurhF*){F;Fr}X)bn=F3i)l+Ic zJOhKky_sKtyax6LrBzr!R8xd3tm3@$!1SK#6%dyScnP_M=ewMh6Y2E6*tJ8}F~5+jlKyg{n1t%G$C*+_99IC7=pbxcM-F#>u0U{*zov>ADJO5#cE z_hJ@CTPSlEzIn=7I4st$HcOU=gE;A*Hw>Y8yj*gtDvlvKFuKtBgc?n|d8Om!Zbvm1 zbI&uAnwhW~e$MDf14L;J`j}ooO*QMMm^iV9`IaAWt1@ zN+T@!I?V(iRZc8F-o@odB(2XW8^K6a%wyYLS>t767|WRY)4ypK_ilxG0(~csO4JE0 z!qpzQeBg!P3gz#eX_UiFRd%)J{IRTuN#>p}+!|hooatW(jYcpE!Z6FcUAu68dAUA% z9>~uBGs5!>AKPwD!mlKVe&ZA6n2}^Tu47Xo+US7szQu69sHmb z23VpJ4Z)r@_YxL_>6p?DZ&%C#SF52AFm~l32SG$tgN%%{H zbrcRWrQj$KNaTnIazeUSZkcl#;)VXMY|XCF>|t84Beo4(vP0Lx^$%D|zC2SKCp4`sL zy-3!#Uz9|}2|4QMUDLSl2A6Y*3c4MO@=qxkvazi@Z`UM*VV4a0<54Cq77aT?B;a=gG=r(b(!i%nnYkRrd_i=E3Y;f9k8=l|l=4*U- zOh_Oi4Ym0MoQ@yuFfR7|v5*V+b;`%`&YhAo?EmG-;#+Fl_f3eUU;L@Lgh%@*rq?bl zqjI?6$Zh?&RcQ=oJFYs#RIhnlu`QDc{x}+DI_@E>FdfLj|^*Vc$z4_UYFXn;ehZ- zvzqnypRWcAC!{cqRM>CT`-e6OiO-AsdVtM061 z>e95yEgMryH5!7eG&RK_JeU}`ks>McJ(Bxwko|VFlosPnNDboPHHcAQ#DSFckk|XF zQ!ZrHgPFgDOc`E!EuSpA%$=~o3aiY`p2PLr9X%wB_sZBWkfGt@xwrgAB}T7C zt@hq2^f6mYG!G!q3L^I!g$MF@CUKP4*5BwSW<{KRsHul2tMH!SAz>MYp!7_WLNCT( zENowRu>-(8KQA{XPOuRDSbYy1Hfrii~=* z?fFJ!Nk!>qgHVWp*>ft5Fn2(@%mWuh5y@=K6*?Ik=3&gq_#sd{;*`AB`yc@eIHAUV z-E{F?D@$%4DV57L#M`6&0_F-GM_pYhvQzEUl*mX}PP5W(1Q_|igPNr0P+e8#;L&B% zLe$J$^x)VxPb+m5wKv*q*GFu2){ABPr(w_6?9SwjtPtO%$@ap(zS9 zr>m%wbKAn*X%6^wMIZLWo{UwNgR;&_#SW%GI$8+(&z@EA-$n42sRs+p10u}9W79Xm zpKWXkS$IQNkCi@o+fvDO?DjCNqjRd{wmaEsZ)j0swJdhhrBp>0ahiyt&sUum*?Aen zM;zupBL=)AzE%N?$RGoGoDQ#dz*#-9@u>B^d>b5;+Faw=cHG@n1*LL|I4+QSwvkW7 zcu?DWr(tEVp_w~+drG0Q+`a8>0<&lbFZ2W*kjZk2{%9V!B2l0eS1CO~WBx8-QX=&d zDE;R&dxh3$GK2vHSwB?!CXok4kIBd*A_o8CL>xrP_WnwzeDGMOa}Zr^3Cq@4A*)D> zY>y(3HCw3*E@5b^ig7+o>~{vsU#l#iCiG?Z%`fX=4Y zkCkOH2kU}tsCpCxEDlkwX3v?u=9ff9(2)>?ST#h zDr|s1T}wu;ay$$2MLFaZCtOnF@!o6KRmxaqpHbpJ(DzWxq{JfwfJD`8ei*odR|QcV zM`uBc=1jAT$0A(z8sR9X!xTyeNzb0L;%c&KFMOt&XPjs zXX@?qedyH>?YoY5Y>LaJt<}w)#UvAH1sc2pq$x!=xZif_viWN!!HbU739X zJdnWDIa{2?TP;oc8JQBSjGYNN;0bdb z4?Hlip?|gVV4OCbi8Y~~cbQ98(zjUKpUAe22WWQaY}-6cg0&5FA-hUR57psK%Oud~ z$?i^iPsh5CI(3*b*5yhP7eOkcM)1GAv&>@U-&!vI5f|f3rZ9o(h3oZ3*gE~?`4H<{ zkjzVDPBI?3kTSX5jBAhd_N`35*y-|8aH=R$V6*IDAM=mo?umOVbW#L0Rp%K`zgtdD zJ;7X{;@BHK3EynwimSDSB7kyh97Kig7z~B_$?08hsI^VrCME5iVAy}IZ(n)AUYV|m z^_FFozjS`jQT~aoNVBE1(qeAT*ZT&lWGEjux-qK2Q64*-E6lPL(j|Em8zjuca-J6Y zQN};*VbHsyN>49~@BNOu5$&3MOUC!(y2SJZ+=CWqI4X^|Coel3O1{Sgx>v^7ly1c( z-MrKLY=I2Wp_y0KwjyhaG#u#tJkiCA?`Zq{Bas~WVYmB^t|U zf+O@7gjb0`Y^th&^Y+n>>I0F5;q_A2XgJA$7<+O zQ&-P?Ch*;{$DXHS+YQAglNSMN1ZrHtwl(E~h zADjPD`uEZiau0-X=?gqdP(*|Lchx{qe_Cg#26)1WZJ5unTAY=)5E*}u85kn^E+G?& z)gPJGQJfuquK;}2bb}<+<|e=QefJG92qH~Dm!bl(uG?PAg};blGc`22#9NH7vJ_6$ zFLH1C?fwUlUKiKJrf-G64j43qAl3VUS$VQV^EvLdTEZLnN)wSPB)t5V!nR}>o`r!P ze0;o1esEPXlMJ=YKr8`H5yff9d{V>zXrP!Os2r($gk05RHumeyzZ&saR40=RaejY- z4G)0y&PIF3l?ByTmQnnpC^tV*Dje@Bqq2cQX!I3pQmluRPsYKD$ z;FqgP>6(Gby#BE-m*Pdp^&gfrdnxF7dMTj43agfad3E<0gsWhYj3}KAr5Mukr>|{m zZQzg%KuBE%9(jUL8mSSE1NjFkrJ-_*M2M-I2H>?nP~dcFt(5F+%8pd%?Bx7ynsGxl zJSr;m+OvRZy@CBypgJt}^jv^)$!W@_&_!h>3si;LgY|L0-95Fx0sm_+KO3~o5bb7< zccVn-m0p+B#+Z>-BX^FdD_yLoKjQxY3vX`h6HFKnuy!oASy?lv=Mej|s)t>q<4Y7T z-Fdrm?ocN4mxv+b(Vy7`az#?|=P}S|*$Ve>vctB#R21ieybTpO5j(~DJlu!qI1lQn zQ9}}7F&Ym`c=6sb7$D7qWf#gLN0E#~&bxCCg4>IXuOp%%Ga&Uz-FhyLOnGXkJ^|g` z*E;Tr#_mbkz16&XAatV=dY*LSHh54f)V#!(33R$2{e-TjsxDITORHpTYVO~yZP*=0 z-w~Xm$|jdR99DGwvkhv<>#D#aeOJhkBQ;Faj3zavr73ey7i~sd+@sN5^6bC_`FF#< zlvxKb#UoX$6ISDZbF#2Jd~(M!Y_C8rZF4yzRW*{pIycrDvLpYo0asHHDGQ&I;LC3F zd*%GT%cahPki2;P*xAuA>>`ty8+#w z4`sht>yT{vB*mH}3EJIIDq)?E%(*9_&qKwseMRMWy*TbiJ|Y*18E4u-*_h%;8H?4< z#rn@VD>s9?X)RZgV&F#bdTmUIv@{D1I)U2AB|Z%Ux}ovO=JNw`&(1Xg|`Nht(0 zi);YF7A`~;SQyEVS>!gjo57f9{(R)$DXo+xh!_Y@Y z)QcPt1o zl)O8|ywjKji`g44pwVwb(}RNDTS}N~zuQ+z@yW;Yl4x9uzNGEfrXU!zl7V zmxrG)KBlO#yj%Hv6OVk7pFe$XdG;cxYzIZ-Cd9%;4Y01OMYdbf%A5ihu+1TTt&hg;NTwlEQW z2~7eCrgV6Bb=BCCBJt@;e>ZPVjiVw>YFGdRVq=2s^X}Ikxtw?21vPG(i(s1(uMG(9 z9ho-W;c1@A*1C4xO}N&M{*`OmP9`kD!dxWnSc+0nS(z$`stahs$ajlR{KUT~hGr7F zLH28Q%H;?GA^Aa(E(^^`9w03rizs%!8~pHcZ~#~lTDw6ZyELd-ZuNeJ9*n%43Yz=G zEg6+r5P-ZHfIn6rohAnnk4ZsE2#;_&=+hWV4;B%PljTi2r%5q)L_qOuA~SV35*ZHE z0fww7nC-BJvCPD={ezi?I20MfDUBg|f(8(*eBTQ?(DNmvsI8d93k4VRPwXsrsX2}=0VM7Tt+Qs@TFKoO&6H@L6`hu{_t z3=zV+0|AFb7wojepBV?nUkjFgm?g8=AQP3?vbRy#>s)*>WY@|x2O$wHYBr68iS~Cq z6`6uT6-s5h0qbdea}viJl9VJqfUm4yNkq(A0o3|aOVucUm5upXC zy_BDX@Z9WO3e&lon!l`!5DDk?@z!#IS??&wapG^VR#VzLj<$7F!AG+i=cZZ3zO&nm zguzDy4oj~y&vF`w_*<+Da7|fPHsHFIg8zAJAguMGVy7~^d1tF?+`QrXCmh7DtkS2V zo1fY}7ld`c$*^o5<&$qovv5$hnCpS_SO|@|5+IH;&OkBsk1J>rGCbF%w6#g78*2xq zJPLuqi0cZcug$V{sM5nq&R6#mQwrNWZBsr3({pL)1h~QF@NUlCACWp~Ckhj;A!P(R znQ#Y!aptk|n7XNF@*~4@+5~P)`hHFxXb7tWBZA;NH>l{4fRk%3uCO@8VpF2;Qfn6^ z-Y(y^;c20(2RjE`2Y-X41L{&h7iLVYilJyHIhfhe;Cap8)qf@qPtFWkaAL)einW?1 zOeJeyKZ`Z-e*qOS^W?d4f2V*e_9$en=)RzPkDDrq1Xq`+)|sE!*CjPSiB>Qy(*yiW za-;|?HMygclR;-9)&sI-qw>W-ho?)#2X!LlIv~wI{Ch9aks1vNJCq_!xR|S5f=o0X z$F@~uXOOgB^XnTn{+T#3!oMR8DLra?ufNP&rn`wY-1eVEiT#^pdk2>8q*Ja z7n^fNKn>^B&QXP*V`f%sqp$a3jt_Azp|6@N^s760;XD1uL3?q*KFivD6_jlyq0XhY zbu49j;K6bh4^D2s+V>lI^G(*%^|&AWeg~+VqaUXTF5c|iwpysDtqb9T@>E|!!XjRv zTqWw#LBbdM1*^uO;S`b>5g~qx5@USt1U88r;($1+qj4g)UGD(4xn6Y|`V=n*{!Cb! zDAJ@r7sHRy&R*x9*yAetP;9KChFYPF&^E~8FHH*PT13Q z3{Kdz_$N4-mwWRzIFs43ZHV@D0CpPQYQ(c-0zT{^9wd>X1re%BM*xauuzxjl8k(WZ zsb$)!!tOpApk;2y&Ut9zJ!;u2`$j!b#WJ==BTGM~l$J9~=~vUCEp$iNj=wgQk5pG< z|I@-F4-J2}&$drhJ-~j^xh`YH-pj6~S>1iVEVu99Je>c&CT!ki`d`+BA^)kJ!iF}c zb|!{S|Gp|*s9tILulR3Uz5gn|{|@oo@n0bz{sm!U=%7-JE)MWZy-J+SkDAyWR`~**_mI z-$%vb%tNLfh2{o){O)~Kr|ILOx~ZaRh)KIRG8UtYVVK3%E@NY}XbHK8WBTjt{I(bR zXqA!}$IpBvPr%RrW~nkKsc*Bi52MmPG#O!)VkU+7zw)EKqvozOw!OM)EK^3H;vD^5 zq^UAz`HR|tvGVAtsD{3K08$pBi-R9D4M{FqtF<8|azmX|2IKi+|J53}mzgdK+L?rs zIj9(~S-F-Hs3_3G*&V$PF;_V+&@+sM9ln5rMSxnDTl7p)h{r}YRk&-eD(S2cPr53a z_#M5sU|Jyw!J$3EE37!Djo}_Li9|0Tk%sC{a@-hS;Hj08!q6No7L>t1?@lTM!$osb z9B9WgbevM)?30Pu^bVZ4f=M?&&szHD-H-!VQg<3J+c_xN8p*&}&&Vwn&eD$U$3>R9 zmVi!qna3`shMqaApo%M- ze=KQ;o8pZ~Yf1904Cs!0#i_)NoBQw?FtCUxu_ohKMVenSzOeD|+*8RCIHd-b3}=$g z;bmT#eQ)3OzaRg)pWfe}-+eu8&vPo# z>?`Br!fdXVls{5AVeSf=5xx+>Pf=bJ*^Ai}fNzrKrit?M>%3ukOZD@3pv~U#^iCMg zOdQn4yS*yVB)Q;6f`zwJ!M%xduUYy*yP+Q+(B@qN_ElS^wwX#8y8sv3r3Vq@u|lFC~cVB4f*m~DR;nX=0J%qeYE zui_*2FZi4H2xS(#=opV6hqKt-$gwU*yk^s(6VlA=B+Ycc6UQKra$PnQs>J0c1L5Qi zu7U866sMQfX0`WJ6SBCJOgOl1$9y6<)&nxA2b^piM9-3gS^mjyfK=M~QfJaWZ5}SuGIWX+GFY;)5_XmJl2Afl*z*{IKdBAx+w_N_B*b4yD-6 zla4eZXa5tURD9zWnMaxH8CZ&NjRdh!oDWgB;M&fK27vnk#kDikZp|JSZ>joo%N40( zj6NvNxDLHI4TEM?&hV_F5B#CJSgvkt!O*VO0xI!mJxM~bv;<|6KL+1skgPpOZvKA! zjsj|4e?Cdo4+y~76YJ#Hz2*M%j2QS^IjO&v%PvbC5kFcoin_YUnI$fzuK>3JLc6<; zF<4fWkF)|$)&iM8t`&Qc#lrQTxm)K{+}OHqjprppd@zaEcmTWlY`|Rd)#8+LZd$Je zTX1DznUwm_T9YJOOVcUvu`5FHWMD)EeqJ;_WtW(o#1whWj=IMLp7$q^u0VkF|D)_3 zqeKgqEa9?k+qP}nwr$?xE!(zj+qUhxWm|LKdtZOkZ`Mq&?jO1GSH@ZEoXm_9J7Vwf zme`Hg-AHHal9F-NGbdDa?Jx~7NGGy&8svaCA<+wg<1vRr4ESM5E-#5x*T)pv#kEs#00o*+}RY?&c) zNzt8h!bwa@{aM`mdj-b0uBoUd*mz8daoc76xuN`N1~GdJ7UXuJa4U%4Ya>`0U>EU+ z`??_I8dv*|_F8$#bjUIArik6v8mK8yNr;Ulrxx@QT}7oy0=l~+z%Q`N(!aLz9ENQ{ zNmS-PE;St>FEkv9mZjl>z4lu&J4td>l#AYV1jRI~#s-*mcDhm{pHEf3chE1xi}^De zPI_}sv}f!@n8tTS!nTZi7c|ieCe>%6WtgyCa;VqeZ4n1J89-48gJe#s0x#AXVcsAbwdxm5~Fh=f8Cw}lC~Yd z`8xiBt?vthev%ktW@AUpKH2+$IHi<|=qm7(&5~UC!2_=fw!=~D1~nO~PjcKStiYx# zQc6YAl0hwFFN21*TNY&1ZVts=KR0A7tT6;aBZglq)thdqrG))d@Eo4qVeJzo&XVQL zXoC`ISi=shvCmL|d1yslHMDURnk~koGkN}DeOPtU;e(9}Tvmmtm+;k2nm*Xv`bz|0 zc3}o>LdAZ-64GJd?2AJP(ayx!je8du0;RZ zGs#^stn-LOtcW&DBQw^WPgzevtkdy4CouOV_yV$qS*8?`saw#ng2$8I$h(1;mul>q zW}uhFs07sD=q28w5oi>h3FyU^ip&0Ve=_8wjvQTJx3-L&_?B|w2F|;CjTv4v`XcD% z5N?oPih93XGv)p#)4mE;HuDemgq#kfWsWv;%7;d3k!DqALU$oz+I;bRPBmiB&j8kX z;XC9CWi5ASAyA!wQ8^SIo+%9p=)O%LCSVuYOz-iGAvug^F~#WQOL8-0zGml(kp*)1 z0k|n(E@KCJR<3Jww_SK?XW}yw zra7#;A}mjGd?8J5_8s{n*-BM6osxRL<(bhzEV1U}W_qx^%MZVcgED#6yBgmIa)@#g zi6GRB1%syrfejWF1)J<_V)tK;jhfJ1WV^8A^oZWSGuo6xShPsiOC`{csQbE1-AnU z$(;g{f=j0x3;*EUHVWD)J5Tok1kkyz|KdNf9H@ImCq#TW7$|LsV9 z0PkX>+PBH;RUfz>vD5R#QY>JaphrI^Fx-~8@y^3uBAfB0pLJrHS?|>%e$SB~`awpY zuP)8=SGdHVmzGqvGohEhk znT2Hiq@zNX&}dkjXG3Q!@l5_v_QhTM_q*Zao5I|?2mRKZALFgvqJW4zZX+pQ;Q37S zt9vi7owO!q%3z?DNe7_$22aPX@j*s{5xxfdw@bmf2i{)~-(cMv_Y>p=As#ZND)_Sv z??@q|915y1V4nkTEzFJ+Md>`23~Rr;Ot~H8L-^@EKcstgZOvX+)t^^GP0@Mvn(6N9 z?(F{^m&603tjPlc05}2vmxj`R1V;ay|1aE}|A&TBwyN%5iYm%KDXJuaAgGt|H3SnY zPo}o)YuHLhoa}p)Yw1^2qR2nK>~1|W6xk`@1kaqOc)Q-dZw#|;ng|db;Ms~`Ch?}3 z-o_2nN>GI+8G#F#q9g2+Nmj7$To6vafNs*Pd+v=sx`(}E7)n>cmp3yj6Gw~?#)x*# zu508PLcs4vpwSvm$T@wHfQZ%$jA);b5PazgU}*ts zjFPeKf4=oLG0!Y#OM_1Mm1ZOopw(W96o=+g6gipq`-pTnB8#Ec7t2H2Yg)a{MxQKB zVwCyVP4tT>Y!cYBwFFXE+0-94zI{*by8P&jJkB_7OkJRp3Ez?ALP(AVU?eoa)NiT5 zZN(aC(`F3@ehL18AM=qT3u;+~SxQ_MBBUD__u%(UxAvIo;cWl{ULD0oRJhIz=0CAp$0IGh z|HnExWk z`LfsPUMb`=eBLEM1V1II>YpiOLRiTN%-`*mU`sAuy?Xqj@Ua(vuJ1kuM;l#zN!eG# zlChA2@_D{?LD~iL=t;{5SUjy}lib|BJq0m}IOqm{kvf=W)_BUcEtI_0-l-`zb(XEm zO)TlPCKB+I&O~DUiPve%T+uR7c317rj;F2-k8RC8PVR5v$X|LnyhOaznJT6V`gJKP>p9s+#$*Jldi>)M5X=hw%yeztMn}u75`ZK>o(^BBpNtLITjWDlGpo z{!1ia9;v@40}uef{x6;FA5i{3R;!pgyNC$=_s>hTqOSGk-}ACs+ZN<+D*^58x)N37 zW|@D%ws~nvC74``t*nXkj=@O$)9sY7;8SII+Ap-yewyQbom%Tv4jEr1T!|^3W+`i* zz4)jlX`X~&L6d!!V)kk-6xM1^;cUj3b<<0Z{hf`_V@>KTAzj2R#Df+jbW&Dvh z$=6%6$2=vaK(ENeS!RhH#&1}vi+?kbcqHNGFL)Xq9IxH-Y-GiIYGM1y4aMDfiAmg+ zA@rDn?LbO;-KD)&k4;ZkIC{rt8~#Ze$4xtD=xbvuJ2r|oMRv@^E|&h9n~l8^LBJK# z`n%}NR`p9`i&w?-j-gXO$@7ky9?etHhyGcsHh_6F6#?H4U(5q%3R9dMFsD9E2qabT z+vq*&dXsIA12b3Ra~20&^g}rQ%3W6PKS57L|Hn@G2X6HDTU#3c2X6GJSz-B)@n7y1 z0h7n{KOg`Ah6w+X0Qe6JN@9{C|NZ^atbSv^$%gQC-5UrRC54AWzb$Sm88!;{_`t`*R)$tYE{OR#psA= z5)Hq322Gu4S;=9hNK40L+-NE%F|0gMk6zu|AXSCYV^@!_{5Dr~s!eOk1+P)Xy=J!( z$w&0KPy+pVTCZcDN1vQ>*@khlQAVP>tpqVTT`P*7)}%#~UbF*H>_Vm}4iHi=k|(-c zj$j9cBtnHT4#BYkC0%bfs#OH?qec^pdLe%9qg8w_Su$Um>deJgklqL7J2XCJ^kTsAGFoKAek-d3<_RxqdxAgKZS>0I5`D z@5ZabGo)I9&Sg^RaS3XpebV&V1OnR>Ue!_uNpQZq+=Uz$m$#=;!dn}`lFKn{rA1I- zqbU(;MjfWPbtTK`XOSbaw14`>gtl}hnlE*{SKaDP%+%4B_kE4balyVz?imyaQ=*E6 zfQV5t*$+l+6Rd|fJACcUwf*SF+he;%^p&RPABJ0)*|}FlJp;+3uf23#;?q-&RYP}0 zLP{DR9VwnRx&o#ea%!lH350o;rSS~Uu%>Db2&KCN@0SN^_-7$#h2azbbj$Q2YQMRt z#g^??CI}Yaqq=qs+zF!r5(NcF@$xdIQ4+&cv8$;GV* zJBi?o^6+xOpN*GD&_9Y@zqR*0ftiT#KBFZd18O!!U^*~+;`jQVH>=BI7O+uReeT5+ zmovIMsFq@-tv`xAIaKlXF{nNnNS?FM79q*(0X2lSqSbHGND8>Th`x=!NrodC30R*0 zvdCY4-$E4qfw~Nj?z1i?@L(0__H}bOfa=f*&0nYy}HeYlVH^yfaYXzl!mEP9x_T(LA=m3#KY) zWB;=TJvG3=6Z6JBBg|J(Wn*LjzLViST0bUU+*rw)yzEY+IL%|a$d@L?s+2qlXLeZF z(0wCjv)OT|(m^%8uH=VQH@zl|YQ;`YjGxXk#LfCc;J`WZJWA1IfbpAJyqQxUl-kwB zEI@+}mASF6)r=3wJVBpIyk%d@Vgi=y;5I0)0U0J6;uk6hp2bwV;~s8Ca4>c%cCV_+ zK{RRv1!z4g$M*NGXi%~+xohhB!kT2--C zop(s!@if4X+JMPm-k;n;$cN9%c9nZF)d>7tx!UfZf0D-kh1$<`{JWn9`+NI}+8SEg z{L52^sa071WBiwQZ&FpOF2&!Z`z$&D0MY-y{-0JvJzSg&ja}^j2Rfn48rseqV*g0L z+n8C9kQyvJmbY;XgRx(8w19`LlRdiBwFAe(LtVhxj1mKRI{RWq=t0Wi*lnIfpSW{YNRsN3Zy9p2oF!&G*AZbULX;oHQ7U<4j5ue( z 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⁣Ky#3AUtu=bmpZT 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%0D;g0O{%X>J)QSOYkI(*rLvlnwp9L@4QO&Zr;qFH? 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 z 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 diff --git a/app/Http/Controllers/Api/AttendanceTracking/AttendanceTrackingController.php b/app/Http/Controllers/Api/AttendanceTracking/AttendanceTrackingController.php index 7348d6ea..bdb79f31 100644 --- a/app/Http/Controllers/Api/AttendanceTracking/AttendanceTrackingController.php +++ b/app/Http/Controllers/Api/AttendanceTracking/AttendanceTrackingController.php @@ -8,7 +8,7 @@ use App\Http\Requests\AttendanceTracking\ParentsInfoRequest; use App\Http\Requests\AttendanceTracking\RecordAttendanceTrackingRequest; use App\Http\Requests\AttendanceTracking\SaveAttendanceNotificationNoteRequest; use App\Http\Requests\AttendanceTracking\SendAttendanceManualEmailRequest; -use App\Services\AttendanceTrackingService; +use App\Services\AttendanceTracking\AttendanceTrackingService; use Illuminate\Http\JsonResponse; use Illuminate\Http\Request; diff --git a/app/Http/Controllers/Api/Auth/RegisterController.php b/app/Http/Controllers/Api/Auth/RegisterController.php new file mode 100644 index 00000000..5ec3221e --- /dev/null +++ b/app/Http/Controllers/Api/Auth/RegisterController.php @@ -0,0 +1,64 @@ +captchaService->generate(); + + return response()->json([ + 'ok' => true, + 'captcha' => $captcha, + ]); + } + + public function store(RegisterRequest $request): JsonResponse + { + if (app()->runningUnitTests() && $request->header('X-Debug-Request') === '1') { + return response()->json([ + 'ok' => true, + 'debug' => [ + 'content_type' => $request->header('Content-Type'), + 'content_length' => strlen((string) $request->getContent()), + 'request_all' => $request->all(), + 'json_all' => $request->json()->all(), + 'payload_data' => $this->payloadData(), + ], + ]); + } + + $result = $this->service->register($request->validated()); + + if (empty($result['ok'])) { + $code = $result['code'] ?? 'registration_failed'; + $status = $code === 'pending_activation' || $code === 'email_exists' ? 409 : 422; + + return response()->json([ + 'ok' => false, + 'message' => $result['message'] ?? 'Registration failed.', + 'code' => $code, + ], $status); + } + + return response()->json([ + 'ok' => true, + 'registration' => new RegisterResource($result), + ], 201); + } +} diff --git a/app/Http/Controllers/Api/ClassPrep/ClassPrepController.php b/app/Http/Controllers/Api/ClassPrep/ClassPrepController.php new file mode 100644 index 00000000..003c13a6 --- /dev/null +++ b/app/Http/Controllers/Api/ClassPrep/ClassPrepController.php @@ -0,0 +1,53 @@ +stickerCounts = $stickerCounts; + $this->roster = $roster; + } + + public function stickerCounts(Request $request): JsonResponse + { + $schoolYear = (string) ($request->query('school_year') ?? ''); + $semester = (string) ($request->query('semester') ?? ''); + $classSectionId = $request->query('class_section_id') ?? $request->query('class_id'); + $classSectionId = is_numeric($classSectionId) ? (int) $classSectionId : null; + + if ($classSectionId !== null && $classSectionId > 0) { + $payload = $this->stickerCounts->listForClass($schoolYear, $semester, $classSectionId); + } else { + $payload = $this->stickerCounts->listAll($schoolYear, $semester); + } + + return response()->json([ + 'ok' => true, + 'data' => $payload, + ]); + } + + public function studentsByClass(Request $request, int $classSectionId): JsonResponse + { + $schoolYear = (string) ($request->query('school_year') ?? ''); + + $students = $this->roster->listStudentsByClass($classSectionId, $schoolYear !== '' ? $schoolYear : null); + + return response()->json([ + 'ok' => true, + 'students' => $students, + ]); + } +} diff --git a/app/Http/Controllers/Api/ClassPreparation/ClassPreparationController.php b/app/Http/Controllers/Api/ClassPreparation/ClassPreparationController.php new file mode 100644 index 00000000..584ff75d --- /dev/null +++ b/app/Http/Controllers/Api/ClassPreparation/ClassPreparationController.php @@ -0,0 +1,60 @@ +service = $service; + } + + public function index(Request $request): JsonResponse + { + $schoolYear = (string) ($request->query('school_year') ?? ''); + $semester = (string) ($request->query('semester') ?? ''); + + $payload = $this->service->listPrep( + $schoolYear !== '' ? $schoolYear : null, + $semester !== '' ? $semester : null + ); + + return response()->json([ + 'ok' => true, + 'schoolYear' => $payload['schoolYear'], + 'semester' => $payload['semester'], + 'results' => $payload['results'], + 'totals' => $payload['totals'], + 'shortages' => $payload['shortages'], + ]); + } + + public function markPrinted(Request $request): JsonResponse + { + $schoolYear = (string) ($request->input('school_year') ?? ''); + $semester = (string) ($request->input('semester') ?? ''); + $ids = $request->input('class_section_ids', []); + + if ($schoolYear === '' || !is_array($ids)) { + return response()->json([ + 'ok' => false, + 'message' => 'school_year and class_section_ids are required.', + ], 422); + } + + $count = $this->service->markPrinted($schoolYear, $semester, $ids); + + return response()->json([ + 'ok' => true, + 'updated' => $count, + ]); + } +} diff --git a/app/Http/Controllers/Api/Communication/CommunicationController.php b/app/Http/Controllers/Api/Communication/CommunicationController.php new file mode 100644 index 00000000..74300f85 --- /dev/null +++ b/app/Http/Controllers/Api/Communication/CommunicationController.php @@ -0,0 +1,178 @@ +students = $students; + $this->templates = $templates; + $this->families = $families; + $this->previewService = $previewService; + $this->sendService = $sendService; + } + + public function options(): JsonResponse + { + return response()->json([ + 'ok' => true, + 'students' => $this->students->listStudents(), + 'templates' => $this->templates->listActiveTemplates(), + ]); + } + + public function families(int $studentId): JsonResponse + { + return response()->json([ + 'ok' => true, + 'data' => $this->families->familiesForStudent($studentId), + ]); + } + + public function guardians(int $familyId): JsonResponse + { + return response()->json([ + 'ok' => true, + 'data' => $this->families->guardiansForFamily($familyId), + ]); + } + + public function preview(Request $request): JsonResponse + { + $templateKey = (string) $request->input('template_key', ''); + $studentId = (int) $request->input('student_id', 0); + $familyId = (int) $request->input('family_id', 0); + $vars = $this->normalizeArray($request->input('vars', [])); + + if ($templateKey === '' || $studentId <= 0 || $familyId <= 0) { + return response()->json([ + 'ok' => false, + 'message' => 'template_key, student_id, and family_id are required.', + ], 422); + } + + $teacherName = $this->resolveTeacherName(); + $result = $this->previewService->buildPreview($templateKey, $studentId, $familyId, $vars, $teacherName); + + if (!$result['ok']) { + return response()->json([ + 'ok' => false, + 'message' => $result['message'] ?? 'Preview failed.', + ], $result['status'] ?? 400); + } + + return response()->json([ + 'ok' => true, + 'subject' => $result['subject'], + 'html' => $result['html'], + ]); + } + + public function send(Request $request): JsonResponse + { + $studentId = (int) $request->input('student_id', 0); + $familyId = (int) $request->input('family_id', 0); + $templateKey = (string) $request->input('template_key', ''); + $subject = (string) $request->input('subject', ''); + $body = (string) $request->input('body', ''); + $recipients = $this->normalizeArray($request->input('recipients', [])); + $cc = $this->normalizeArray($request->input('cc', [])); + $bcc = $this->normalizeArray($request->input('bcc', [])); + + if ($studentId <= 0 || $familyId <= 0 || $templateKey === '' || $subject === '' || $body === '') { + return response()->json([ + 'ok' => false, + 'message' => 'student_id, family_id, template_key, subject, and body are required.', + ], 422); + } + + if (empty($recipients)) { + return response()->json([ + 'ok' => false, + 'message' => 'At least one recipient is required.', + ], 422); + } + + $student = $this->students->getStudentBasic($studentId); + if (!$student) { + return response()->json([ + 'ok' => false, + 'message' => 'Student not found.', + ], 404); + } + + $result = $this->sendService->send([ + 'student_id' => $studentId, + 'family_id' => $familyId, + 'template_key' => $templateKey, + 'subject' => $subject, + 'body' => $body, + 'recipients' => $recipients, + 'cc' => $cc, + 'bcc' => $bcc, + 'student_name' => trim(($student['firstname'] ?? '') . ' ' . ($student['lastname'] ?? '')), + 'sent_by' => (int) (auth()->id() ?? 0), + ]); + + if (!$result['ok']) { + return response()->json([ + 'ok' => false, + 'message' => 'Failed to send email.', + 'error' => $result['error'], + ], 500); + } + + return response()->json([ + 'ok' => true, + 'message' => 'Email sent successfully.', + ]); + } + + private function normalizeArray($value): array + { + if (is_array($value)) { + return $value; + } + + if (is_string($value) && $value !== '') { + $decoded = json_decode($value, true); + if (is_array($decoded)) { + return $decoded; + } + } + + return []; + } + + private function resolveTeacherName(): string + { + $user = auth()->user(); + if ($user) { + $name = trim(($user->firstname ?? '') . ' ' . ($user->lastname ?? '')); + return $name !== '' ? $name : 'Teacher'; + } + return 'Teacher'; + } +} diff --git a/app/Http/Controllers/Api/CompetitionScores/CompetitionScoresController.php b/app/Http/Controllers/Api/CompetitionScores/CompetitionScoresController.php new file mode 100644 index 00000000..52b96100 --- /dev/null +++ b/app/Http/Controllers/Api/CompetitionScores/CompetitionScoresController.php @@ -0,0 +1,212 @@ +contextService = $contextService; + $this->queryService = $queryService; + $this->rosterService = $rosterService; + $this->saveService = $saveService; + } + + public function index(Request $request): JsonResponse + { + $userId = (int) (auth()->id() ?? 0); + [$assignments, $schoolYear, $semester] = $this->contextService->getTeacherContext($userId); + + $activeClassId = $this->contextService->resolveActiveClassId( + $assignments, + (int) $request->query('class_section_id', 0) + ); + $activeClassName = $this->contextService->getActiveClassName($assignments, $activeClassId); + + if (empty($assignments) || $activeClassId <= 0) { + return response()->json([ + 'ok' => true, + 'competitions' => [], + 'activeClassId' => $activeClassId, + 'activeClassName' => $activeClassName, + 'questionCounts' => [], + 'scoreCounts' => [], + 'studentTotal' => 0, + 'sectionMap' => $this->contextService->getClassSectionMap(), + 'schoolYear' => $schoolYear, + 'semester' => $semester, + 'hasClasses' => false, + ]); + } + + $competitions = $this->queryService->getCompetitionsForClass($activeClassId, $schoolYear, $semester); + $competitionIds = array_values(array_filter(array_map(static function ($row) { + return (int) ($row['id'] ?? 0); + }, $competitions))); + + return response()->json([ + 'ok' => true, + 'competitions' => $competitions, + 'activeClassId' => $activeClassId, + 'activeClassName' => $activeClassName, + 'questionCounts' => $this->queryService->getQuestionCounts($competitionIds, $activeClassId), + 'scoreCounts' => $this->queryService->getScoreCounts($competitionIds, $activeClassId), + 'studentTotal' => $this->rosterService->getClassStudentCount($activeClassId, $schoolYear), + 'sectionMap' => $this->contextService->getClassSectionMap(), + 'schoolYear' => $schoolYear, + 'semester' => $semester, + 'hasClasses' => true, + ]); + } + + public function edit(Request $request, int $id): JsonResponse + { + $userId = (int) (auth()->id() ?? 0); + [$assignments, $schoolYear, $semester] = $this->contextService->getTeacherContext($userId); + $allowedClassIds = $this->contextService->getAllowedClassIds($assignments); + + if (empty($allowedClassIds)) { + return response()->json([ + 'ok' => false, + 'message' => 'No class assignments found for your account.', + ], 403); + } + + $competition = Competition::query()->find($id); + if (!$competition) { + return response()->json([ + 'ok' => false, + 'message' => 'Competition not found.', + ], 404); + } + + $isLocked = (bool) $competition->is_locked; + $lockedClassId = (int) ($competition->class_section_id ?? 0); + $requestedClassId = (int) $request->query('class_section_id', 0); + $activeClassId = $this->contextService->resolveActiveClassId($assignments, $requestedClassId); + $classSectionId = $lockedClassId > 0 ? $lockedClassId : $activeClassId; + + if ($classSectionId <= 0) { + return response()->json([ + 'ok' => false, + 'message' => 'Select a class section before entering scores.', + ], 422); + } + + if (!in_array($classSectionId, $allowedClassIds, true)) { + return response()->json([ + 'ok' => false, + 'message' => 'You are not assigned to that class.', + ], 403); + } + + $competitionArray = $competition->toArray(); + $students = $this->rosterService->getStudentsForCompetition($competitionArray, $classSectionId); + $scoreMap = $this->saveService->getScoreMap($id, $classSectionId); + $questionCount = $this->queryService->getQuestionCountForCompetition($id, $classSectionId); + $classStudentCount = $this->rosterService->getClassStudentCount( + $classSectionId, + $competitionArray['school_year'] ?? $schoolYear + ); + + return response()->json([ + 'ok' => true, + 'competition' => $competitionArray, + 'students' => $students, + 'scoreMap' => $scoreMap, + 'classSectionId' => $classSectionId, + 'classSectionName' => $this->contextService->getActiveClassName($assignments, $classSectionId), + 'classStudentCount' => $classStudentCount, + 'questionCount' => $questionCount, + 'classSelectionLocked' => $lockedClassId > 0, + 'isLocked' => $isLocked, + 'schoolYear' => $schoolYear, + 'semester' => $semester, + ]); + } + + public function save(Request $request, int $id): JsonResponse + { + $userId = (int) (auth()->id() ?? 0); + [$assignments] = $this->contextService->getTeacherContext($userId); + $allowedClassIds = $this->contextService->getAllowedClassIds($assignments); + + if (empty($allowedClassIds)) { + return response()->json([ + 'ok' => false, + 'message' => 'No class assignments found for your account.', + ], 403); + } + + $competition = Competition::query()->find($id); + if (!$competition) { + return response()->json([ + 'ok' => false, + 'message' => 'Competition not found.', + ], 404); + } + + if ((bool) $competition->is_locked) { + return response()->json([ + 'ok' => false, + 'message' => 'Competition is locked. You cannot update scores.', + ], 423); + } + + $lockedClassId = (int) ($competition->class_section_id ?? 0); + $classSectionId = $lockedClassId > 0 ? $lockedClassId : (int) $request->input('class_section_id', 0); + + if ($classSectionId <= 0) { + return response()->json([ + 'ok' => false, + 'message' => 'Select a class section before saving scores.', + ], 422); + } + + if (!in_array($classSectionId, $allowedClassIds, true)) { + return response()->json([ + 'ok' => false, + 'message' => 'You are not assigned to that class.', + ], 403); + } + + $scores = $request->input('scores', []); + $scores = is_array($scores) ? $scores : []; + [$cleanScores, $invalidScores] = $this->saveService->filterScores($scores); + + if (!empty($invalidScores)) { + return response()->json([ + 'ok' => false, + 'message' => 'Scores must be whole numbers (no decimals).', + 'invalidScores' => $invalidScores, + ], 422); + } + + $this->saveService->saveScores($id, $classSectionId, $cleanScores); + + return response()->json([ + 'ok' => true, + 'message' => 'Scores saved.', + 'savedCount' => count($cleanScores), + ]); + } +} diff --git a/app/Http/Controllers/Api/Discounts/DiscountController.php b/app/Http/Controllers/Api/Discounts/DiscountController.php new file mode 100644 index 00000000..d7885a8d --- /dev/null +++ b/app/Http/Controllers/Api/Discounts/DiscountController.php @@ -0,0 +1,107 @@ +context = $context; + $this->voucherService = $voucherService; + $this->parentService = $parentService; + $this->applyService = $applyService; + } + + public function options(): JsonResponse + { + $schoolYear = $this->context->getSchoolYear(); + $vouchers = $this->voucherService->listActive(); + $parents = $this->parentService->listParentsWithDiscounts($schoolYear); + + return response()->json([ + 'ok' => true, + 'vouchers' => DiscountVoucherResource::collection($vouchers), + 'parents' => DiscountParentResource::collection($parents), + 'schoolYear' => $schoolYear, + ]); + } + + public function apply(ApplyDiscountVoucherRequest $request): JsonResponse + { + $payload = $request->validated(); + $result = $this->applyService->applyVoucher( + (int) $payload['voucher_id'], + $payload['parent_ids'], + (bool) ($payload['allow_additional'] ?? false), + (int) (auth()->id() ?? 0) + ); + + return response()->json($result, $result['ok'] ? 200 : 422); + } + + public function listVouchers(): JsonResponse + { + $vouchers = $this->voucherService->listAll(); + return response()->json([ + 'ok' => true, + 'vouchers' => DiscountVoucherResource::collection($vouchers), + ]); + } + + public function storeVoucher(StoreDiscountVoucherRequest $request): JsonResponse + { + $voucher = $this->voucherService->create($request->validated()); + return response()->json([ + 'ok' => true, + 'voucher' => new DiscountVoucherResource($voucher), + ], 201); + } + + public function updateVoucher(UpdateDiscountVoucherRequest $request, int $id): JsonResponse + { + $voucher = $this->voucherService->update($id, $request->validated()); + return response()->json([ + 'ok' => true, + 'voucher' => new DiscountVoucherResource($voucher), + ]); + } + + public function showVoucher(int $id): JsonResponse + { + $voucher = $this->voucherService->find($id); + return response()->json([ + 'ok' => true, + 'voucher' => new DiscountVoucherResource($voucher), + ]); + } + + public function deleteVoucher(int $id): JsonResponse + { + $this->voucherService->delete($id); + return response()->json([ + 'ok' => true, + ]); + } +} diff --git a/app/Http/Controllers/Api/Email/BroadcastEmailController.php b/app/Http/Controllers/Api/Email/BroadcastEmailController.php new file mode 100644 index 00000000..16fd3d93 --- /dev/null +++ b/app/Http/Controllers/Api/Email/BroadcastEmailController.php @@ -0,0 +1,184 @@ +senderOptions = $senderOptions; + $this->recipients = $recipients; + $this->composer = $composer; + $this->dispatch = $dispatch; + $this->imageService = $imageService; + } + + public function options(): JsonResponse + { + return response()->json([ + 'ok' => true, + 'parents' => $this->recipients->parentsWithEmails(), + 'fromOptions' => $this->senderOptions->listOptions(), + ]); + } + + public function send(Request $request): JsonResponse + { + $mode = (string) ($request->input('mode') ?? 'standard'); + $subject = trim((string) ($request->input('subject') ?? '')); + $fromKey = trim((string) ($request->input('from_key') ?? 'general')); + $body = (string) ($request->input('body_html') ?? ''); + $wrapLayout = (bool) $request->boolean('wrap_layout'); + $preheader = (string) ($request->input('preheader') ?? ''); + $ctaText = (string) ($request->input('cta_text') ?? ''); + $ctaUrl = (string) ($request->input('cta_url') ?? ''); + $testEmail = trim((string) ($request->input('test_email') ?? '')); + $isTestOnly = (bool) $request->boolean('send_test_only'); + + $body = $this->composer->sanitizeHtml($body); + + if ($subject === '' || $body === '') { + return response()->json([ + 'ok' => false, + 'message' => 'Subject and Body are required.', + ], 422); + } + + $personalize = $mode === 'personalized'; + + if ($isTestOnly) { + if ($testEmail === '') { + return response()->json([ + 'ok' => false, + 'message' => 'Provide a test email address.', + ], 422); + } + + $result = $this->dispatch->sendTest([ + 'wrap_layout' => $wrapLayout, + 'subject' => $subject, + 'body_html' => $body, + 'recipient_name' => 'Parent', + 'preheader' => $preheader, + 'cta_text' => $ctaText, + 'cta_url' => $ctaUrl, + 'personalize' => $personalize, + 'test_email' => $testEmail, + 'from_key' => $fromKey, + ]); + + return response()->json($result, $result['ok'] ? 200 : 500); + } + + $rawIds = $request->input('parent_ids', []); + $ids = $this->normalizeIds($rawIds); + + if (empty($ids)) { + return response()->json([ + 'ok' => false, + 'message' => 'Please select at least one parent.', + ], 422); + } + + $recipients = $this->recipients->recipientsByIds($ids); + + if (empty($recipients)) { + return response()->json([ + 'ok' => false, + 'message' => 'No valid parent emails found.', + ], 422); + } + + $stats = $this->dispatch->sendBroadcast([ + 'wrap_layout' => $wrapLayout, + 'subject' => $subject, + 'body_html' => $body, + 'preheader' => $preheader, + 'cta_text' => $ctaText, + 'cta_url' => $ctaUrl, + 'personalize' => $personalize, + 'from_key' => $fromKey, + 'mode' => $mode, + ], $recipients); + + $message = sprintf( + 'Broadcast finished. Mode: %s. Sent: %d/%d. Failures: %d.', + $stats['mode'], + $stats['sent'], + $stats['attempted'], + $stats['failed'] + ); + + return response()->json([ + 'ok' => $stats['failed'] === 0, + 'stats' => $stats, + 'message' => $message, + ]); + } + + public function uploadImage(Request $request): JsonResponse + { + $file = $request->file('image'); + if (!$file || !$file->isValid()) { + return response()->json([ + 'success' => false, + 'error' => 'No image uploaded', + ], 400); + } + + $result = $this->imageService->store($file); + if (!$result['ok']) { + return response()->json([ + 'success' => false, + 'error' => $result['error'] ?? 'Upload failed', + ], $result['status'] ?? 400); + } + + $newHash = function_exists('csrf_hash') ? csrf_hash() : csrf_token(); + + return response()->json([ + 'success' => true, + 'url' => $result['url'], + 'csrf_hash' => $newHash, + ])->header('X-CSRF-HASH', (string) $newHash); + } + + private function normalizeIds($rawIds): array + { + $ids = []; + $raw = is_array($rawIds) ? $rawIds : [$rawIds]; + + foreach ($raw as $value) { + if (is_string($value) && strpos($value, ',') !== false) { + foreach (explode(',', $value) as $piece) { + $ids[] = (int) $piece; + } + continue; + } + $ids[] = (int) $value; + } + + return array_values(array_unique(array_filter($ids))); + } +} diff --git a/app/Http/Controllers/Api/Expenses/ExpenseController.php b/app/Http/Controllers/Api/Expenses/ExpenseController.php new file mode 100644 index 00000000..726f5cb7 --- /dev/null +++ b/app/Http/Controllers/Api/Expenses/ExpenseController.php @@ -0,0 +1,180 @@ +context->getSchoolYear(); + $semester = $this->context->getSemester(); + + return response()->json([ + 'ok' => true, + 'schoolYear' => $schoolYear, + 'semester' => $semester, + 'retailors' => $this->retailors->listRetailors(), + 'staff' => ExpenseStaffResource::collection($this->staff->listStaffUsers()), + ]); + } + + public function index(): JsonResponse + { + $rows = $this->queryService->listAll(); + $rows = array_map(function ($row) { + $row['receipt_url'] = $this->receiptService->receiptUrl($row['receipt_path'] ?? null); + return $row; + }, $rows); + + return response()->json([ + 'ok' => true, + 'expenses' => ExpenseResource::collection($rows), + ]); + } + + public function show(int $id): JsonResponse + { + $row = $this->queryService->findById($id); + if (!$row) { + return response()->json(['ok' => false, 'message' => 'Expense not found.'], 404); + } + + $row['receipt_url'] = $this->receiptService->receiptUrl($row['receipt_path'] ?? null); + + return response()->json([ + 'ok' => true, + 'expense' => new ExpenseResource($row), + ]); + } + + public function store(StoreExpenseRequest $request): JsonResponse + { + $payload = $request->validated(); + $userId = (int) (auth()->id() ?? 0); + + $receiptName = null; + if ($request->hasFile('receipt')) { + $receiptName = $this->receiptService->storeReceipt($request->file('receipt')); + } + + $isDonation = $payload['category'] === 'Donation'; + + $expense = $this->writeService->store([ + 'category' => $payload['category'], + 'amount' => $payload['amount'], + 'receipt_path' => $receiptName, + 'description' => $payload['description'] ?? null, + 'retailor' => $payload['retailor'] ?? null, + 'date_of_purchase' => $payload['date_of_purchase'], + 'purchased_by' => (int) $payload['purchased_by'], + 'added_by' => $userId, + 'status' => $isDonation ? 'approved' : 'pending', + 'status_reason' => $isDonation ? 'Marked as Donation (non-reimbursable).' : null, + 'approved_by' => $isDonation ? $userId : null, + 'school_year' => $payload['school_year'] ?? null, + 'semester' => $payload['semester'] ?? null, + ]); + + return response()->json([ + 'ok' => true, + 'expense' => new ExpenseResource($expense->toArray()), + ], 201); + } + + public function update(UpdateExpenseRequest $request, int $id): JsonResponse + { + $expense = Expense::query()->find($id); + if (!$expense) { + return response()->json(['ok' => false, 'message' => 'Expense not found.'], 404); + } + + $payload = $request->validated(); + $userId = (int) (auth()->id() ?? 0); + + $receiptName = $expense->receipt_path; + if ($request->hasFile('receipt')) { + $receiptName = $this->receiptService->storeReceipt($request->file('receipt')); + } + if (!empty($payload['remove_receipt'])) { + $receiptName = null; + } + + $category = $payload['category']; + $isDonation = $category === 'Donation'; + + $updateData = [ + 'category' => $category, + 'amount' => $payload['amount'], + 'description' => $payload['description'] ?? null, + 'retailor' => $payload['retailor'] ?? null, + 'date_of_purchase' => $payload['date_of_purchase'], + 'purchased_by' => (int) $payload['purchased_by'], + 'receipt_path' => $receiptName, + 'updated_by' => $userId, + ]; + + if ($isDonation) { + $updateData['status'] = 'approved'; + $updateData['status_reason'] = 'Marked as Donation (non-reimbursable).'; + $updateData['approved_by'] = $userId ?: null; + $updateData['reimbursement_id'] = null; + } elseif (($expense->category ?? '') === 'Donation') { + $updateData['status_reason'] = null; + $updateData['approved_by'] = $expense->approved_by; + $updateData['status'] = $expense->status ?? 'pending'; + } + + $updated = $this->writeService->update($expense, $updateData); + + return response()->json([ + 'ok' => true, + 'expense' => new ExpenseResource($updated->toArray()), + ]); + } + + public function updateStatus(UpdateExpenseStatusRequest $request, int $id): JsonResponse + { + $expense = Expense::query()->find($id); + if (!$expense) { + return response()->json(['ok' => false, 'message' => 'Expense not found.'], 404); + } + + $payload = $request->validated(); + $userId = (int) (auth()->id() ?? 0); + + $updated = $this->statusService->updateStatus($expense, $payload['status'], $payload['reason'] ?? '', $userId); + + return response()->json([ + 'ok' => true, + 'expense' => new ExpenseResource($updated->toArray()), + ]); + } +} diff --git a/app/Http/Controllers/Api/ExtraCharges/ExtraChargesController.php b/app/Http/Controllers/Api/ExtraCharges/ExtraChargesController.php new file mode 100644 index 00000000..a9126454 --- /dev/null +++ b/app/Http/Controllers/Api/ExtraCharges/ExtraChargesController.php @@ -0,0 +1,143 @@ +query('school_year') ?? $this->context->getSchoolYear()); + $sem = (string) ($request->query('semester') ?? $this->context->getSemester()); + $status = $request->query('status') ?: null; + $q = trim((string) ($request->query('q') ?? '')) ?: null; + $per = (int) ($request->query('per_page') ?? 50); + + [$rows, $meta] = $this->listService->listForTerm($year, $sem, $status, $q, $per); + + return response()->json([ + 'ok' => true, + 'school_year' => $year, + 'semester' => $sem, + 'rows' => ExtraChargeResource::collection($rows), + 'pager' => $meta, + ]); + } + + public function options(Request $request): JsonResponse + { + $q = trim((string) ($request->query('q') ?? '')); + $parentOptions = $this->parents->searchParents($q); + + return response()->json([ + 'ok' => true, + 'schoolYear' => $this->context->getSchoolYear(), + 'semester' => $this->context->getSemester(), + 'schoolYears' => $this->meta->getSchoolYears($this->context->getSchoolYear()), + 'parents' => ExtraChargeParentOptionResource::collection($parentOptions), + ]); + } + + public function parentOptions(Request $request): JsonResponse + { + $q = trim((string) ($request->query('q') ?? '')); + $parentOptions = $this->parents->searchParents($q); + + return response()->json([ + 'results' => ExtraChargeParentOptionResource::collection($parentOptions), + ]); + } + + public function invoicesForParent(Request $request): JsonResponse + { + $parentId = (int) ($request->query('parent_id') ?? 0); + $schoolYear = (string) ($request->query('school_year') ?? $this->context->getSchoolYear()); + $rows = $this->invoices->listInvoicesForParent($parentId, $schoolYear); + + return response()->json([ + 'results' => ExtraChargeInvoiceOptionResource::collection($rows), + ]); + } + + public function store(StoreExtraChargeRequest $request): JsonResponse + { + $payload = $request->validated(); + $payload['created_by'] = (int) (auth()->id() ?? 0); + + $result = $this->chargeService->createCharge($payload); + + return response()->json($result, $result['ok'] ? 201 : 422); + } + + public function update(UpdateExtraChargeRequest $request, int $id): JsonResponse + { + $charge = AdditionalCharge::query()->find($id); + if (!$charge) { + return response()->json(['ok' => false, 'error' => 'Charge not found'], 404); + } + + $payload = $request->validated(); + $payload['updated_by'] = (int) (auth()->id() ?? 0); + + $ok = $this->chargeService->updateCharge($charge, $payload); + + return response()->json([ + 'ok' => $ok, + 'id' => $id, + ], $ok ? 200 : 422); + } + + public function void(int $id): JsonResponse + { + $charge = AdditionalCharge::query()->find($id); + if (!$charge) { + return response()->json(['ok' => false, 'error' => 'Charge not found'], 404); + } + + $ok = $this->chargeService->voidCharge($charge); + + return response()->json([ + 'ok' => $ok, + ], $ok ? 200 : 422); + } + + public function reverse(int $id): JsonResponse + { + $charge = AdditionalCharge::query()->find($id); + if (!$charge) { + return response()->json(['ok' => false, 'error' => 'Charge not found'], 404); + } + + $ok = $this->chargeService->reverseCharge($charge); + + return response()->json([ + 'ok' => $ok, + ], $ok ? 200 : 422); + } +} diff --git a/app/Http/Controllers/Api/Finance/FinancialController.php b/app/Http/Controllers/Api/Finance/FinancialController.php new file mode 100644 index 00000000..45d84525 --- /dev/null +++ b/app/Http/Controllers/Api/Finance/FinancialController.php @@ -0,0 +1,218 @@ +validated(); + $report = $this->reportService->getReport( + $payload['date_from'] ?? null, + $payload['date_to'] ?? null, + $payload['school_year'] ?? null + ); + + return response()->json([ + 'ok' => true, + 'report' => new FinancialReportResource($report), + ]); + } + + public function summary(FinancialSummaryRequest $request): JsonResponse + { + $payload = $request->validated(); + $summary = $this->summaryService->getSummary( + $payload['date_from'] ?? null, + $payload['date_to'] ?? null, + $payload['school_year'] ?? null + ); + + $schoolYears = $this->schoolYears->listYears($summary['schoolYear'] ?? null); + + return response()->json([ + 'ok' => true, + 'summary' => new FinancialSummaryResource($summary), + 'schoolYears' => $schoolYears, + ]); + } + + public function unpaidParents(FinancialUnpaidParentsRequest $request): JsonResponse + { + $payload = $request->validated(); + $data = $this->unpaidParentsService->getUnpaidParents($payload['school_year'] ?? null); + + return response()->json([ + 'ok' => true, + 'schoolYear' => $data['schoolYear'] ?? null, + 'schoolYears' => $data['schoolYears'] ?? [], + 'results' => FinancialUnpaidParentResource::collection($data['results'] ?? []), + ]); + } + + public function downloadCsv(FinancialReportRequest $request): StreamedResponse + { + $payload = $request->validated(); + $report = $this->reportService->getReport( + $payload['date_from'] ?? null, + $payload['date_to'] ?? null, + $payload['school_year'] ?? null + ); + + $paymentsMap = []; + foreach ($report['payments'] as $payment) { + $invoiceId = (int) ($payment['invoice_id'] ?? 0); + $paymentsMap[$invoiceId] = (float) ($payment['paid_amount'] ?? 0); + } + + $refundsMap = []; + foreach ($report['refunds'] as $refund) { + $invoiceId = (int) ($refund['invoice_id'] ?? 0); + $refundsMap[$invoiceId] = (float) ($refund['total_refunded'] ?? 0); + } + + $discountsMap = []; + foreach ($report['discounts'] as $discount) { + $invoiceId = (int) ($discount['invoice_id'] ?? 0); + $discountsMap[$invoiceId] = (float) ($discount['discount_amount'] ?? 0); + } + + $breakdown = $report['paymentBreakdown'] ?? []; + + $filename = 'financial_report_' . date('Ymd_His') . '.csv'; + + return response()->streamDownload(function () use ($report, $paymentsMap, $refundsMap, $discountsMap, $breakdown) { + $out = fopen('php://output', 'w'); + + fputcsv($out, ['Invoice #', 'Parent', 'School Year', 'Total', 'Paid', 'Cash', 'Credit', 'Check', 'Balance', 'Refund', 'Discount', 'Status']); + $sumTotal = 0.0; + $sumPaid = 0.0; + $sumCash = 0.0; + $sumCredit = 0.0; + $sumCheck = 0.0; + $sumBalance = 0.0; + $sumRefund = 0.0; + $sumDiscount = 0.0; + + foreach ($report['invoices'] as $inv) { + $invoiceId = (int) ($inv['id'] ?? 0); + $paid = (float) ($paymentsMap[$invoiceId] ?? 0); + $bd = $breakdown[$invoiceId] ?? []; + $cash = (float) ($bd['cash'] ?? 0); + $credit = (float) ($bd['credit'] ?? 0); + $check = (float) ($bd['check'] ?? 0); + $refunded = (float) ($refundsMap[$invoiceId] ?? 0); + $discount = (float) ($discountsMap[$invoiceId] ?? 0); + $total = (float) ($inv['total_amount'] ?? 0); + $balance = $total - $paid - $discount - $refunded; + if ($balance < 0) { + $balance = 0.0; + } + $status = $balance === 0.0 ? 'Paid' : 'Unpaid'; + + fputcsv($out, [ + $inv['invoice_number'] ?? '', + $inv['parent_name'] ?? '', + $inv['school_year'] ?? '', + $total, + $paid, + $cash, + $credit, + $check, + $balance, + $refunded, + $discount, + $status, + ]); + + $sumTotal += $total; + $sumPaid += $paid; + $sumCash += $cash; + $sumCredit += $credit; + $sumCheck += $check; + $sumBalance += $balance; + $sumRefund += $refunded; + $sumDiscount += $discount; + } + + fputcsv($out, [ + 'TOTALS', + '', + '', + $sumTotal, + $sumPaid, + $sumCash, + $sumCredit, + $sumCheck, + $sumBalance, + $sumRefund, + $sumDiscount, + '', + ]); + + fputcsv($out, []); + fputcsv($out, ['Expenses Summary']); + fputcsv($out, ['Category', 'Total Amount']); + foreach ($report['expenses'] as $exp) { + fputcsv($out, [ + $exp['category'] ?? '', + $exp['total_amount'] ?? 0, + ]); + } + + fputcsv($out, []); + fputcsv($out, ['Reimbursements Summary']); + fputcsv($out, ['Status', 'Total Amount']); + foreach ($report['reimbursements'] as $row) { + fputcsv($out, [ + $row['status'] ?? '', + $row['total_amount'] ?? 0, + ]); + } + + fclose($out); + }, $filename, ['Content-Type' => 'text/csv']); + } + + public function downloadPdf(FinancialReportRequest $request): StreamedResponse + { + $payload = $request->validated(); + $summary = $this->summaryService->getSummary( + $payload['date_from'] ?? null, + $payload['date_to'] ?? null, + $payload['school_year'] ?? null + ); + + $pdfBytes = $this->pdfService->buildPdf($summary); + $schoolYear = $summary['schoolYear'] ?? 'report'; + + return response()->streamDownload(function () use ($pdfBytes) { + echo $pdfBytes; + }, 'Financial_Report_' . $schoolYear . '.pdf', ['Content-Type' => 'application/pdf']); + } +} diff --git a/app/Http/Controllers/Api/Finance/InvoiceController.php b/app/Http/Controllers/Api/Finance/InvoiceController.php new file mode 100644 index 00000000..944187af --- /dev/null +++ b/app/Http/Controllers/Api/Finance/InvoiceController.php @@ -0,0 +1,125 @@ +validated()['school_year'] ?? null; + $schoolYear = $schoolYear ?: $request->input('schoolYear'); + $schoolYear = $schoolYear ?: $request->input('year'); + + $data = $this->management->getManagementData($schoolYear); + + return response()->json([ + 'ok' => true, + 'schoolYear' => $data['schoolYear'], + 'schoolYears' => $data['schoolYears'], + 'invoices' => InvoiceManagementParentResource::collection($data['invoices']), + ]); + } + + public function generate(InvoiceGenerateRequest $request): JsonResponse + { + $payload = $request->validated(); + $result = $this->generation->generateInvoice((int) $payload['parent_id'], $payload['school_year'] ?? null); + + if (empty($result['ok'])) { + return response()->json(['ok' => false, 'message' => $result['message'] ?? 'Unable to generate invoice.'], 422); + } + + return response()->json([ + 'ok' => true, + 'updated' => $result['updated'] ?? false, + 'updated_ids' => $result['updated_ids'] ?? [], + 'insert_id' => $result['insert_id'] ?? null, + ]); + } + + public function byParent(InvoiceParentRequest $request, int $parentId): JsonResponse + { + $schoolYear = $request->validated()['school_year'] ?? null; + $invoices = Invoice::getInvoicesByParentId($parentId, $schoolYear); + + return response()->json([ + 'ok' => true, + 'invoices' => InvoiceResource::collection($invoices), + ]); + } + + public function parentPayment(InvoiceParentRequest $request): JsonResponse + { + $parentId = (int) (auth()->id() ?? 0); + if ($parentId <= 0) { + return response()->json(['ok' => false, 'message' => 'Unauthorized.'], 401); + } + + $schoolYear = $request->validated()['school_year'] ?? null; + $data = $this->paymentService->getParentInvoiceSummary($parentId, $schoolYear); + + return response()->json([ + 'ok' => true, + 'invoices' => InvoiceResource::collection($data['invoices']), + 'schoolYears' => $data['schoolYears'], + 'selectedYear' => $data['selectedYear'], + 'currentSchoolYear' => $data['currentSchoolYear'], + 'dueDate' => $data['dueDate'], + ]); + } + + public function updateStatus(InvoiceStatusRequest $request, int $invoiceId): JsonResponse + { + $status = $request->validated()['status']; + $updated = Invoice::updateInvoiceStatus($invoiceId, $status); + + if (!$updated) { + return response()->json(['ok' => false, 'message' => 'Invoice not found.'], 404); + } + + return response()->json(['ok' => true, 'status' => $status]); + } + + public function unpaid(): JsonResponse + { + $rows = Invoice::getUnpaidInvoices(); + + return response()->json([ + 'ok' => true, + 'invoices' => InvoiceResource::collection($rows), + ]); + } + + public function pdf(int $invoiceId): StreamedResponse + { + $pdfBytes = $this->pdfService->buildPdf($invoiceId); + + return response()->streamDownload(function () use ($pdfBytes) { + echo $pdfBytes; + }, 'invoice_' . $invoiceId . '.pdf', ['Content-Type' => 'application/pdf']); + } +} diff --git a/app/Http/Controllers/Api/Finance/PaymentController.php b/app/Http/Controllers/Api/Finance/PaymentController.php new file mode 100644 index 00000000..8b044593 --- /dev/null +++ b/app/Http/Controllers/Api/Finance/PaymentController.php @@ -0,0 +1,58 @@ +validated(); + $payment = $this->planService->createPlan($payload); + + return response()->json([ + 'ok' => true, + 'payment' => new PaymentResource($payment), + ], 201); + } + + public function byParent(PaymentByParentRequest $request, int $parentId): JsonResponse + { + $payload = $request->validated(); + $payments = $this->lookupService->getByParent($parentId, $payload['school_year'] ?? null); + + return response()->json([ + 'ok' => true, + 'payments' => PaymentResource::collection($payments), + ]); + } + + public function updateBalance(PaymentUpdateBalanceRequest $request, int $paymentId): JsonResponse + { + $payload = $request->validated(); + $updated = $this->balanceService->updateBalance($paymentId, (float) $payload['paid_amount']); + + if (!$updated) { + return response()->json(['ok' => false, 'message' => 'Payment not found.'], 404); + } + + return response()->json(['ok' => true]); + } +} diff --git a/app/Http/Controllers/Api/Finance/PaymentEventChargesController.php b/app/Http/Controllers/Api/Finance/PaymentEventChargesController.php new file mode 100644 index 00000000..fa6dafd9 --- /dev/null +++ b/app/Http/Controllers/Api/Finance/PaymentEventChargesController.php @@ -0,0 +1,46 @@ +validated(); + $data = $this->service->listCharges($payload['school_year'] ?? null, $payload['semester'] ?? null); + + return response()->json(['ok' => true] + $data); + } + + public function store(PaymentEventChargesStoreRequest $request): JsonResponse + { + $payload = $request->validated(); + $userId = (int) (auth()->id() ?? 0); + + $count = $this->service->addCharges($payload, $userId); + if ($count === 0) { + return response()->json(['ok' => false, 'message' => 'No student selected.'], 422); + } + + return response()->json(['ok' => true, 'inserted' => $count]); + } + + public function enrolledStudents(PaymentEventChargesListRequest $request, int $parentId): JsonResponse + { + $payload = $request->validated(); + $students = $this->service->getEnrolledStudents($parentId, $payload['school_year'] ?? null); + + return response()->json(['ok' => true, 'students' => $students]); + } +} diff --git a/app/Http/Controllers/Api/Finance/PaymentManualController.php b/app/Http/Controllers/Api/Finance/PaymentManualController.php new file mode 100644 index 00000000..5cd5f466 --- /dev/null +++ b/app/Http/Controllers/Api/Finance/PaymentManualController.php @@ -0,0 +1,81 @@ +validated(); + $data = $this->service->search( + (string) ($payload['search_term'] ?? ''), + $payload['school_year'] ?? null + ); + + return response()->json(['ok' => true] + $data); + } + + public function suggest(PaymentManualSuggestRequest $request): JsonResponse + { + $payload = $request->validated(); + $items = $this->service->suggest((string) ($payload['q'] ?? '')); + + return response()->json(['ok' => true, 'items' => $items]); + } + + public function record(PaymentManualUpdateRequest $request, int $invoiceId): JsonResponse + { + $payload = $request->validated(); + + $result = $this->service->recordPayment( + $invoiceId, + (float) $payload['amount'], + (string) $payload['payment_method'], + $payload['payment_date'] ?? null, + $payload['check_number'] ?? null, + $payload['payment_type'] ?? null, + $request->file('payment_file'), + $payload['school_year'] ?? null, + $payload['semester'] ?? null, + (int) (auth()->id() ?? 0) + ); + + return response()->json(['ok' => true] + $result); + } + + public function edit(PaymentManualEditRequest $request, int $paymentId): JsonResponse + { + $payload = $request->validated(); + + $this->service->editPayment( + $paymentId, + (float) $payload['paid_amount'], + (string) $payload['payment_method'], + $payload['check_number'] ?? null, + $request->file('payment_file'), + (int) (auth()->id() ?? 0) + ); + + return response()->json(['ok' => true]); + } + + public function file(Request $request, string $filename) + { + $mode = (string) ($request->query('mode') ?? 'download'); + return $this->service->serveCheckFile($filename, $mode); + } +} diff --git a/app/Http/Controllers/Api/Finance/PaymentNotificationController.php b/app/Http/Controllers/Api/Finance/PaymentNotificationController.php new file mode 100644 index 00000000..206f97dc --- /dev/null +++ b/app/Http/Controllers/Api/Finance/PaymentNotificationController.php @@ -0,0 +1,41 @@ +validated(); + $logs = $this->service->listLogs( + $payload['from'] ?? null, + $payload['to'] ?? null, + $payload['type'] ?? null + ); + + return response()->json([ + 'ok' => true, + 'logs' => PaymentNotificationLogResource::collection($logs), + ]); + } + + public function send(PaymentNotificationSendRequest $request): JsonResponse + { + $payload = $request->validated(); + $result = $this->service->send($payload); + + return response()->json(['ok' => true] + $result); + } +} diff --git a/app/Http/Controllers/Api/Finance/PaymentTransactionController.php b/app/Http/Controllers/Api/Finance/PaymentTransactionController.php new file mode 100644 index 00000000..80954c58 --- /dev/null +++ b/app/Http/Controllers/Api/Finance/PaymentTransactionController.php @@ -0,0 +1,51 @@ +validated(); + $transaction = $this->service->create($payload); + + return response()->json([ + 'ok' => true, + 'transaction' => new PaymentTransactionResource($transaction->toArray()), + ], 201); + } + + public function byPayment(int $paymentId): JsonResponse + { + $transactions = $this->service->getByPayment($paymentId); + + return response()->json([ + 'ok' => true, + 'transactions' => PaymentTransactionResource::collection($transactions), + ]); + } + + public function updateStatus(PaymentTransactionStatusRequest $request, string $transactionId): JsonResponse + { + $payload = $request->validated(); + $updated = $this->service->updateStatus($transactionId, $payload['status']); + + if (!$updated) { + return response()->json(['ok' => false, 'message' => 'Transaction not found.'], 404); + } + + return response()->json(['ok' => true]); + } +} diff --git a/app/Http/Controllers/Api/Finance/PaypalPaymentController.php b/app/Http/Controllers/Api/Finance/PaypalPaymentController.php new file mode 100644 index 00000000..cfaf9530 --- /dev/null +++ b/app/Http/Controllers/Api/Finance/PaypalPaymentController.php @@ -0,0 +1,52 @@ +json([ + 'ok' => true, + 'redirect_url' => $this->service->getRedirectUrl(), + ]); + } + + public function create(int $paymentId): JsonResponse + { + $result = $this->service->createPayment($paymentId); + + return response()->json(['ok' => true] + $result); + } + + public function execute(PaypalExecuteRequest $request): JsonResponse + { + $payload = $request->validated(); + $this->service->executePayment( + (string) $payload['payer_id'], + $payload['paypal_payment_id'] ?? null + ); + + return response()->json(['ok' => true]); + } + + public function cancel(): JsonResponse + { + $this->service->cancelPayment(); + + return response()->json([ + 'ok' => true, + 'message' => 'Payment was canceled.', + ]); + } +} diff --git a/app/Http/Controllers/Api/Finance/PaypalTransactionsController.php b/app/Http/Controllers/Api/Finance/PaypalTransactionsController.php new file mode 100644 index 00000000..46764bbf --- /dev/null +++ b/app/Http/Controllers/Api/Finance/PaypalTransactionsController.php @@ -0,0 +1,71 @@ +validated(); + $perPage = (int) ($payload['per_page'] ?? 10); + $rows = $this->service->list($payload['q'] ?? null, $perPage); + + return response()->json([ + 'ok' => true, + 'transactions' => PaypalTransactionResource::collection($rows->items()), + 'pagination' => [ + 'current_page' => $rows->currentPage(), + 'per_page' => $rows->perPage(), + 'total' => $rows->total(), + 'last_page' => $rows->lastPage(), + ], + ]); + } + + public function downloadCsv(PaypalTransactionsListRequest $request): StreamedResponse + { + $payload = $request->validated(); + $rows = $this->service->listAll($payload['q'] ?? null); + + $filename = 'paypal_transactions_' . date('Ymd_His') . '.csv'; + + return response()->streamDownload(function () use ($rows) { + $out = fopen('php://output', 'w'); + fputcsv($out, [ + 'ID', 'Transaction ID', 'Order ID', 'Parent School ID', + 'Email', 'Amount', 'Net Amount', 'Currency', + 'Status', 'Event Type', 'Created At', + ]); + + foreach ($rows as $row) { + fputcsv($out, [ + $row['id'] ?? null, + $row['transaction_id'] ?? null, + $row['order_id'] ?? null, + $row['parent_school_id'] ?? null, + $row['payer_email'] ?? null, + $row['amount'] ?? null, + $row['net_amount'] ?? null, + $row['currency'] ?? null, + $row['status'] ?? null, + $row['event_type'] ?? null, + $row['created_at'] ?? null, + ]); + } + + fclose($out); + }, $filename, ['Content-Type' => 'text/csv']); + } +} diff --git a/app/Http/Controllers/Api/Finance/ReimbursementController.php b/app/Http/Controllers/Api/Finance/ReimbursementController.php new file mode 100644 index 00000000..4a173cb6 --- /dev/null +++ b/app/Http/Controllers/Api/Finance/ReimbursementController.php @@ -0,0 +1,391 @@ +queryService->underProcessing(); + + return response()->json([ + 'ok' => true, + 'pendingItems' => ReimbursementUnderProcessingItemResource::collection($data['pendingItems'] ?? []), + 'existingBatches' => ReimbursementBatchResource::collection($data['existingBatches'] ?? []), + 'adminUsers' => ReimbursementRecipientResource::collection($data['adminUsers'] ?? []), + 'itemsPayload' => ReimbursementUnderProcessingItemResource::collection($data['itemsPayload'] ?? []), + ]); + } + + public function markDonation(ReimbursementMarkDonationRequest $request): JsonResponse + { + $payload = $request->validated(); + $userId = (int) (auth()->id() ?? 0); + + try { + $this->donationService->markDonation((int) $payload['expense_id'], $userId); + } catch (RuntimeException $e) { + $message = $e->getMessage(); + $status = str_contains($message, 'not found') ? 404 : (str_contains($message, 'already') ? 409 : 422); + return response()->json(['ok' => false, 'message' => $message], $status); + } catch (\Throwable $e) { + return response()->json(['ok' => false, 'message' => 'Unable to mark donation right now.'], 500); + } + + return response()->json(['ok' => true]); + } + + public function createBatch(ReimbursementBatchCreateRequest $request): JsonResponse + { + $payload = $request->validated(); + $userId = (int) (auth()->id() ?? 0); + $schoolYear = $this->context->getSchoolYear(); + $semester = $this->context->getSemester(); + + $batch = $this->batchService->createBatch($payload['title'] ?? null, $userId, $schoolYear, $semester); + + return response()->json([ + 'ok' => true, + 'batch' => $batch, + ], 201); + } + + public function updateBatchAssignment(ReimbursementBatchAssignmentRequest $request): JsonResponse + { + $payload = $request->validated(); + + $batchId = (int) ($payload['batch_id'] ?? 0); + $batchNumber = $payload['batch_number'] ?? null; + if ($batchId <= 0 && $batchNumber !== null && trim((string) $batchNumber) !== '') { + $batchId = (int) $batchNumber; + } + + $adminIdRaw = $payload['admin_id'] ?? null; + $adminId = ($adminIdRaw === '' || $adminIdRaw === null) ? null : (int) $adminIdRaw; + $reimbursementId = !empty($payload['reimbursement_id']) ? (int) $payload['reimbursement_id'] : null; + + try { + $result = $this->assignmentService->updateAssignment( + (int) $payload['expense_id'], + $batchId, + $adminId, + $reimbursementId, + $this->context->getSchoolYear(), + $this->context->getSemester() + ); + } catch (RuntimeException $e) { + return response()->json(['ok' => false, 'message' => $e->getMessage()], 422); + } + + return response()->json([ + 'ok' => true, + 'assignment' => $result, + ]); + } + + public function lockBatch(ReimbursementBatchLockRequest $request): JsonResponse + { + $payload = $request->validated(); + $userId = (int) (auth()->id() ?? 0); + + try { + $this->batchService->lockBatch( + (int) $payload['batch_id'], + $userId, + $this->context->getSchoolYear(), + $this->context->getSemester() + ); + } catch (RuntimeException $e) { + $message = $e->getMessage(); + $status = str_contains($message, 'check file') ? 409 : 404; + return response()->json(['ok' => false, 'message' => $message], $status); + } catch (\Throwable $e) { + return response()->json(['ok' => false, 'message' => 'Unable to lock batch right now.'], 500); + } + + return response()->json(['ok' => true, 'status' => 'closed']); + } + + public function uploadBatchAdminFile(ReimbursementBatchAdminFileRequest $request): JsonResponse + { + $payload = $request->validated(); + $batchId = (int) $payload['batch_id']; + $adminId = (int) $payload['admin_id']; + + $batch = ReimbursementBatch::query()->find($batchId); + if (!$batch) { + return response()->json(['ok' => false, 'message' => 'Batch not found.'], 404); + } + if (strtolower((string) ($batch->status ?? '')) !== ReimbursementBatch::STATUS_OPEN) { + return response()->json(['ok' => false, 'message' => 'Cannot upload files for a locked batch.'], 409); + } + + try { + $file = $this->fileService->storeAdminFile($batchId, $adminId, $request->file('check_file'), (int) (auth()->id() ?? 0)); + } catch (\Throwable $e) { + return response()->json(['ok' => false, 'message' => 'Unable to store the uploaded file.'], 500); + } + + return response()->json([ + 'ok' => true, + 'file' => $file, + ]); + } + + public function serveAdminCheckFile(FileNameRequest $request, string $name, string $mode = 'inline'): Response|JsonResponse + { + $allowedExtensions = ['jpg', 'jpeg', 'png', 'webp', 'gif', 'pdf']; + $meta = $this->fileServeService->meta(storage_path('uploads/reimbursements'), $name, $allowedExtensions, null); + $disposition = strtolower(trim($mode)) === 'download' ? 'attachment' : 'inline'; + + return response(file_get_contents($meta['path']), 200, [ + 'Content-Type' => $meta['mime'], + 'Content-Disposition' => $disposition . '; filename="' . $meta['download_name'] . '"', + 'Content-Length' => (string) $meta['size'], + 'ETag' => $meta['etag'], + 'Last-Modified' => $meta['last_modified'], + ]); + } + + public function index(ReimbursementIndexRequest $request): JsonResponse + { + $payload = $request->validated(); + $filters = [ + 'school_year' => $payload['school_year'] ?? null, + 'semester' => $payload['semester'] ?? null, + 'status' => $payload['status'] ?? null, + 'user_id' => $payload['user_id'] ?? null, + ]; + + $data = $this->queryService->index($filters); + + return response()->json([ + 'ok' => true, + 'expenses' => ReimbursementExpenseResource::collection($data['expenses'] ?? []), + 'users' => ReimbursementRecipientResource::collection($data['users'] ?? []), + 'schoolYears' => $data['schoolYears'] ?? [], + 'batchSummaries' => $data['batchSummaries'] ?? [], + 'batchDetails' => $data['batchDetails'] ?? [], + 'batchAttachments' => $data['batchAttachments'] ?? [], + 'donationBatch' => $data['donationBatch'] ?? null, + 'donationDetails' => $data['donationDetails'] ?? null, + ]); + } + + public function sendBatchEmail(ReimbursementBatchEmailRequest $request): JsonResponse + { + $payload = $request->validated(); + $receiptIds = $payload['receipts'] ?? []; + $checkIds = $payload['checks'] ?? []; + + try { + $ok = $this->emailService->sendBatchEmail( + (int) $payload['batch_id'], + $payload['recipient_email'], + (string) ($payload['message'] ?? ''), + $receiptIds, + $checkIds + ); + } catch (RuntimeException $e) { + return response()->json(['ok' => false, 'message' => $e->getMessage()], 404); + } + + if (!$ok) { + return response()->json(['ok' => false, 'message' => 'Failed to send email. Please try again.'], 500); + } + + return response()->json(['ok' => true, 'message' => 'Email sent successfully.']); + } + + public function export(ReimbursementExportRequest $request): Response + { + $payload = $request->validated(); + $type = $payload['type'] ?? 'processed'; + + if ($type === 'under_processing') { + $csv = $this->exportService->buildUnderProcessingCsv(); + } else { + $csv = $this->exportService->buildProcessedCsv($payload); + } + + return response()->streamDownload(function () use ($csv) { + $out = fopen('php://output', 'w'); + fprintf($out, chr(0xEF) . chr(0xBB) . chr(0xBF)); + foreach ($csv['rows'] as $row) { + fputcsv($out, $row); + } + fclose($out); + }, $csv['filename'], [ + 'Content-Type' => 'text/csv; charset=UTF-8', + ]); + } + + public function exportBatch(ReimbursementExportBatchRequest $request): Response + { + $payload = $request->validated(); + $csv = $this->exportService->buildBatchCsv((int) $payload['batch_id']); + + return response()->streamDownload(function () use ($csv) { + $out = fopen('php://output', 'w'); + fprintf($out, chr(0xEF) . chr(0xBB) . chr(0xBF)); + foreach ($csv['rows'] as $row) { + fputcsv($out, $row); + } + fclose($out); + }, $csv['filename'], [ + 'Content-Type' => 'text/csv; charset=UTF-8', + ]); + } + + public function store(ReimbursementStoreRequest $request): JsonResponse + { + $payload = $request->validated(); + $userId = (int) (auth()->id() ?? 0); + $receiptName = null; + + if ($request->hasFile('receipt')) { + $receiptName = $this->fileService->storeReceipt($request->file('receipt')); + } + + try { + $reimb = $this->crudService->store( + $payload, + $userId, + $this->context->getSchoolYear(), + $this->context->getSemester(), + $receiptName + ); + } catch (RuntimeException $e) { + return response()->json(['ok' => false, 'message' => $e->getMessage()], 422); + } + + $data = $reimb->toArray(); + $data['receipt_url'] = $this->fileService->receiptUrl($reimb->receipt_path ?? null); + + return response()->json([ + 'ok' => true, + 'reimbursement' => new ReimbursementResource($data), + ], 201); + } + + public function process(ReimbursementProcessRequest $request): JsonResponse + { + $payload = $request->validated(); + $userId = (int) (auth()->id() ?? 0); + $receiptName = null; + + if ($request->hasFile('receipt')) { + $receiptName = $this->fileService->storeReceipt($request->file('receipt')); + } + + try { + $reimb = $this->crudService->process( + $payload, + $userId, + $this->context->getSchoolYear(), + $this->context->getSemester(), + $receiptName + ); + } catch (RuntimeException $e) { + return response()->json(['ok' => false, 'message' => $e->getMessage()], 422); + } + + $data = $reimb->toArray(); + $data['receipt_url'] = $this->fileService->receiptUrl($reimb->receipt_path ?? null); + + return response()->json([ + 'ok' => true, + 'reimbursement' => new ReimbursementResource($data), + ], 201); + } + + public function reimbursedExpenses(): JsonResponse + { + $expenses = $this->queryService->reimbursedExpenses(); + + return response()->json([ + 'ok' => true, + 'expenses' => $expenses, + ]); + } + + public function update(ReimbursementUpdateRequest $request, int $id): JsonResponse + { + $reimb = Reimbursement::query()->find($id); + if (!$reimb) { + return response()->json(['ok' => false, 'message' => 'Reimbursement not found.'], 404); + } + + $payload = $request->validated(); + $userId = (int) (auth()->id() ?? 0); + + $receiptName = $reimb->receipt_path; + if ($request->hasFile('receipt')) { + $receiptName = $this->fileService->storeReceipt($request->file('receipt')); + } + if (!empty($payload['remove_receipt'])) { + $receiptName = null; + } + + $updated = $this->crudService->update($reimb, $payload, $userId, $receiptName); + $data = $updated->toArray(); + $data['receipt_url'] = $this->fileService->receiptUrl($updated->receipt_path ?? null); + + return response()->json([ + 'ok' => true, + 'reimbursement' => new ReimbursementResource($data), + ]); + } +} diff --git a/app/Http/Controllers/Api/Grading/GradingController.php b/app/Http/Controllers/Api/Grading/GradingController.php new file mode 100644 index 00000000..11516b36 --- /dev/null +++ b/app/Http/Controllers/Api/Grading/GradingController.php @@ -0,0 +1,281 @@ +validated(); + $data = $this->overviewService->overview( + isset($payload['class_id']) ? (int) $payload['class_id'] : null, + $payload['semester'] ?? null, + $payload['school_year'] ?? null + ); + + return response()->json(['ok' => true] + $data); + } + + public function show(GradingScoreShowRequest $request, string $type, int $classSectionId, int $studentId): JsonResponse + { + $payload = $request->validated(); + $data = $this->scoreService->show( + $type, + $classSectionId, + $studentId, + (string) $payload['semester'], + (string) $payload['school_year'] + ); + + return response()->json([ + 'ok' => true, + 'student' => $data['student'], + 'scores' => GradingScoreResource::collection($data['scores']), + 'type' => $data['type'], + 'class_section_id' => $data['class_section_id'], + 'semester' => $data['semester'], + 'scores_locked' => $data['scores_locked'], + ]); + } + + public function update(GradingScoreUpdateRequest $request): JsonResponse + { + $payload = $request->validated(); + $this->scoreService->update($payload, (int) (auth()->id() ?? 0)); + + return response()->json(['ok' => true]); + } + + public function toggleLock(GradingLockRequest $request): JsonResponse + { + $payload = $request->validated(); + $locked = $this->lockService->toggle( + (int) $payload['class_section_id'], + (string) $payload['semester'], + (string) $payload['school_year'], + (int) (auth()->id() ?? 0) + ); + + return response()->json(['ok' => true, 'locked' => $locked]); + } + + public function lockAll(GradingLockAllRequest $request): JsonResponse + { + $payload = $request->validated(); + $count = $this->lockService->lockAll( + (string) $payload['semester'], + (string) $payload['school_year'], + (int) (auth()->id() ?? 0) + ); + + return response()->json(['ok' => true, 'locked_count' => $count]); + } + + public function refresh(GradingRefreshRequest $request): JsonResponse + { + $payload = $request->validated(); + $this->refreshService->refreshSemesterScores( + (int) $payload['class_section_id'], + (string) $payload['semester'], + (string) $payload['school_year'] + ); + + return response()->json(['ok' => true]); + } + + public function placement(PlacementRequest $request): JsonResponse + { + $payload = $request->validated(); + $data = $this->placementService->placementContext( + isset($payload['class_section_id']) ? (int) $payload['class_section_id'] : null, + (string) $payload['school_year'], + $payload['placement_test'] ?? null, + $payload['open'] ?? null + ); + + return response()->json(['ok' => true] + $data); + } + + public function updatePlacementLevel(PlacementLevelRequest $request): JsonResponse + { + $payload = $request->validated(); + $this->placementService->updatePlacementLevel( + (int) $payload['student_id'], + (string) $payload['school_year'], + $payload['placement_level'] ?? null, + (int) (auth()->id() ?? 0) + ); + + return response()->json(['ok' => true]); + } + + public function updatePlacementLevels(PlacementLevelsRequest $request): JsonResponse + { + $payload = $request->validated(); + $this->placementService->updatePlacementLevels( + (int) $payload['class_section_id'], + (string) $payload['school_year'], + $payload['placement_level'] ?? [], + (int) (auth()->id() ?? 0) + ); + + return response()->json(['ok' => true]); + } + + public function createPlacementBatch(PlacementBatchRequest $request): JsonResponse + { + $payload = $request->validated(); + $batchId = $this->placementService->createPlacementBatch( + (string) $payload['school_year'], + (string) $payload['placement_test'], + $payload['placement_level'] ?? [], + (int) (auth()->id() ?? 0) + ); + + return response()->json(['ok' => true, 'batch_id' => $batchId]); + } + + public function showPlacementBatch(int $batchId): JsonResponse + { + $data = $this->placementService->getPlacementBatch($batchId); + + return response()->json(['ok' => true] + $data); + } + + public function updatePlacementBatch(PlacementBatchUpdateRequest $request, int $batchId): JsonResponse + { + $payload = $request->validated(); + $this->placementService->updatePlacementBatch( + $batchId, + (string) $payload['school_year'], + $payload['placement_level'] ?? [], + (int) (auth()->id() ?? 0) + ); + + return response()->json(['ok' => true]); + } + + public function belowSixty(BelowSixtyListRequest $request): JsonResponse + { + $payload = $request->validated(); + $rows = $this->belowSixtyService->listRows( + (string) $payload['school_year'], + (string) $payload['semester'] + ); + + return response()->json([ + 'ok' => true, + 'rows' => BelowSixtyStudentResource::collection($rows), + 'semester' => $payload['semester'], + 'school_year' => $payload['school_year'], + ]); + } + + public function belowSixtyEmail(BelowSixtyEmailRequest $request): JsonResponse + { + $payload = $request->validated(); + $data = $this->belowSixtyService->emailContext( + (int) $payload['student_id'], + (string) $payload['school_year'], + (string) $payload['semester'] + ); + + return response()->json(['ok' => true] + $data); + } + + public function sendBelowSixtyEmail(BelowSixtySendRequest $request): JsonResponse + { + $payload = $request->validated(); + $data = $this->belowSixtyService->emailContext( + (int) $payload['student_id'], + (string) $payload['school_year'], + (string) $payload['semester'] + ); + + $subject = trim((string) ($payload['subject'] ?? '')); + if ($subject !== '') { + $data['subject'] = $subject; + } + if (isset($payload['html']) && trim((string) $payload['html']) !== '') { + $data['html'] = (string) $payload['html']; + } + + $this->belowSixtyService->sendEmail($data); + + return response()->json(['ok' => true]); + } + + public function updateBelowSixtyStatus(BelowSixtyStatusRequest $request): JsonResponse + { + $payload = $request->validated(); + $this->belowSixtyService->updateStatus( + (int) $payload['student_id'], + (string) $payload['semester'], + (string) $payload['school_year'], + (string) $payload['status'], + (string) ($payload['note'] ?? ''), + (int) (auth()->id() ?? 0) + ); + + return response()->json(['ok' => true]); + } + + public function belowSixtyMeeting(BelowSixtyMeetingRequest $request): JsonResponse + { + $payload = $request->validated(); + $data = $this->belowSixtyService->meetingContext( + (int) $payload['student_id'], + (string) $payload['school_year'], + (string) $payload['semester'] + ); + + return response()->json(['ok' => true] + $data); + } + + public function saveBelowSixtyMeeting(BelowSixtyMeetingSaveRequest $request): JsonResponse + { + $payload = $request->validated(); + $this->belowSixtyService->saveMeeting($payload, (int) (auth()->id() ?? 0)); + + return response()->json(['ok' => true]); + } +} diff --git a/app/Http/Controllers/Api/Grading/HomeworkTrackingController.php b/app/Http/Controllers/Api/Grading/HomeworkTrackingController.php new file mode 100644 index 00000000..17f873e6 --- /dev/null +++ b/app/Http/Controllers/Api/Grading/HomeworkTrackingController.php @@ -0,0 +1,45 @@ +validated(); + $data = $this->service->report( + $payload['semester'] ?? null, + $payload['school_year'] ?? null, + isset($payload['page']) ? (int) $payload['page'] : 1 + ); + + return response()->json([ + 'ok' => true, + 'semester' => $data['semester'], + 'school_year' => $data['school_year'], + 'sundays' => $data['sundays'], + 'event_days' => $data['event_days'], + 'date_to_index' => $data['date_to_index'], + 'teachers' => HomeworkTrackingTeacherResource::collection($data['teachers']), + 'has_homework' => $data['has_homework'], + 'hw_entered_at' => $data['hw_entered_at'], + 'has_homework_by_date' => $data['has_homework_by_date'], + 'hw_entered_at_by_date' => $data['hw_entered_at_by_date'], + 'page' => $data['page'], + 'total_pages' => $data['total_pages'], + 'per_page' => $data['per_page'], + 'total_rows' => $data['total_rows'], + ]); + } +} diff --git a/app/Http/Controllers/Api/Incidents/IncidentController.php b/app/Http/Controllers/Api/Incidents/IncidentController.php new file mode 100644 index 00000000..1d30d344 --- /dev/null +++ b/app/Http/Controllers/Api/Incidents/IncidentController.php @@ -0,0 +1,151 @@ +currentService->listCurrent(); + + return response()->json([ + 'ok' => true, + 'incidents' => IncidentResource::collection($data['incidents']), + 'grades' => IncidentGradeResource::collection($data['grades']), + ]); + } + + public function history(IncidentListRequest $request): JsonResponse + { + $payload = $request->validated(); + $incidents = $this->historyService->history($payload['school_year'] ?? null, $payload['semester'] ?? null); + + return response()->json([ + 'ok' => true, + 'incidents' => IncidentResource::collection($incidents), + ]); + } + + public function processed(IncidentListRequest $request): JsonResponse + { + $payload = $request->validated(); + $incidents = $this->historyService->processed($payload['school_year'] ?? null, $payload['semester'] ?? null); + + return response()->json([ + 'ok' => true, + 'incidents' => IncidentResource::collection($incidents), + ]); + } + + public function analysis(IncidentListRequest $request): JsonResponse + { + $payload = $request->validated(); + $students = $this->analysisService->analyze($payload['school_year'] ?? null, $payload['semester'] ?? null); + + return response()->json([ + 'ok' => true, + 'students' => IncidentAnalysisStudentResource::collection($students), + 'school_year' => $payload['school_year'] ?? null, + 'semester' => $payload['semester'] ?? null, + ]); + } + + public function studentsByGrade(int $gradeId): JsonResponse + { + $students = $this->currentService->studentsByGrade($gradeId); + + return response()->json([ + 'ok' => true, + 'students' => IncidentStudentOptionResource::collection($students), + ]); + } + + public function store(IncidentStoreRequest $request): JsonResponse + { + $payload = $request->validated(); + $result = $this->currentService->addIncident($payload, (int) (auth()->id() ?? 0)); + + if (empty($result['ok'])) { + return response()->json(['ok' => false, 'message' => $result['message'] ?? 'Failed to add incident.'], 422); + } + + return response()->json([ + 'ok' => true, + 'created' => $result['created'], + 'incident_id' => $result['incident_id'], + ]); + } + + public function updateState(IncidentStateRequest $request, int $incidentId): JsonResponse + { + $payload = $request->validated(); + $updated = $this->currentService->updateState($incidentId, (string) $payload['incident_state']); + + return response()->json([ + 'ok' => $updated, + ]); + } + + public function close(IncidentCloseRequest $request, int $incidentId): JsonResponse + { + $payload = $request->validated(); + $result = $this->currentService->closeIncident( + $incidentId, + (string) $payload['state_description'], + $payload['action_taken'] ?? null, + (int) (auth()->id() ?? 0) + ); + + if (empty($result['ok'])) { + return response()->json(['ok' => false, 'message' => $result['message'] ?? 'Failed to close incident.'], 422); + } + + return response()->json([ + 'ok' => true, + 'incident_id' => $result['incident_id'], + ]); + } + + public function cancel(IncidentCancelRequest $request, int $incidentId): JsonResponse + { + $payload = $request->validated(); + $result = $this->currentService->cancelIncident( + $incidentId, + $payload['state_description'] ?? null, + $payload['action_taken'] ?? null, + (int) (auth()->id() ?? 0) + ); + + if (empty($result['ok'])) { + return response()->json(['ok' => false, 'message' => $result['message'] ?? 'Failed to cancel incident.'], 422); + } + + return response()->json([ + 'ok' => true, + 'incident_id' => $result['incident_id'], + ]); + } +} diff --git a/app/Http/Controllers/Api/Reports/FilesController.php b/app/Http/Controllers/Api/Reports/FilesController.php new file mode 100644 index 00000000..29ec1197 --- /dev/null +++ b/app/Http/Controllers/Api/Reports/FilesController.php @@ -0,0 +1,105 @@ +serveFile( + $request, + storage_path('uploads/receipts'), + $name, + ['jpg', 'jpeg', 'png', 'webp', 'gif', 'pdf'], + null, + false + ); + } + + public function reimb(FileNameRequest $request, string $name): Response|JsonResponse + { + return $this->serveFile( + $request, + storage_path('uploads/reimbursements'), + $name, + ['jpg', 'jpeg', 'png', 'webp', 'gif', 'pdf'], + null, + true + ); + } + + public function earlyDismissalSignature(FileNameRequest $request, string $name): Response|JsonResponse + { + return $this->serveFile( + $request, + storage_path('uploads/early_dismissal_signatures'), + $name, + ['jpg', 'jpeg', 'png', 'webp', 'gif', 'pdf'], + null, + true + ); + } + + public function examDraftTeacher(FileNameRequest $request, string $name): Response|JsonResponse + { + $downloadName = $this->draftNameService->build($name, 'drafts'); + + return $this->serveFile( + $request, + storage_path('uploads/exams/drafts'), + $name, + ['doc', 'docx', 'pdf'], + $downloadName, + true + ); + } + + public function examDraftFinal(FileNameRequest $request, string $name): Response|JsonResponse + { + $downloadName = $this->draftNameService->build($name, 'finals'); + + return $this->serveFile( + $request, + storage_path('uploads/exams/finals'), + $name, + ['doc', 'docx', 'pdf'], + $downloadName, + true + ); + } + + private function serveFile( + FileNameRequest $request, + string $baseDir, + string $name, + array $allowedExtensions, + ?string $downloadName, + bool $nosniff + ): Response|JsonResponse { + if ($request->boolean('meta')) { + $meta = $this->fileService->meta($baseDir, $name, $allowedExtensions, $downloadName); + + return response()->json([ + 'ok' => true, + 'file' => new FileMetaResource($meta), + ]); + } + + return $this->fileService->serveInline($baseDir, $name, $allowedExtensions, $request, $downloadName, $nosniff); + } +} diff --git a/app/Http/Controllers/Api/Scores/FinalController.php b/app/Http/Controllers/Api/Scores/FinalController.php new file mode 100644 index 00000000..7ba0f37b --- /dev/null +++ b/app/Http/Controllers/Api/Scores/FinalController.php @@ -0,0 +1,67 @@ +validated(); + $data = $this->service->list('final_exam', (int) $payload['class_section_id'], $payload['semester'] ?? null, $payload['school_year'] ?? null); + + return response()->json([ + 'ok' => true, + 'students' => ScoreStudentResource::collection($data['students']), + 'semester' => $data['semester'], + 'school_year' => $data['schoolYear'], + 'scores_locked' => $data['scoresLocked'], + 'missing_ok_map' => $data['missingOkMap'], + ]); + } + + public function update(ScoreUpdateRequest $request): JsonResponse + { + $payload = $request->validated(); + $count = $this->service->update( + 'final_exam', + (int) $payload['class_section_id'], + (string) $payload['semester'], + (string) $payload['school_year'], + $payload['scores'], + $payload['missing_ok'] ?? [], + (int) (auth()->id() ?? 0) + ); + + $this->refreshSemesterScores((int) $payload['class_section_id'], (string) $payload['semester'], (string) $payload['school_year']); + + return response()->json(['ok' => true, 'updated' => $count]); + } + + private function refreshSemesterScores(int $classSectionId, string $semester, string $schoolYear): void + { + $studentInfo = Student::getStudentInfoByClassSectionId($classSectionId, $semester, $schoolYear, (int) (auth()->id() ?? 0)); + if (empty($studentInfo)) { + return; + } + try { + $this->semesterScoreService->updateScoresForStudents($studentInfo, $semester, $schoolYear); + } catch (\Throwable $e) { + } + } +} diff --git a/app/Http/Controllers/Api/Scores/HomeworkController.php b/app/Http/Controllers/Api/Scores/HomeworkController.php new file mode 100644 index 00000000..a78ab68f --- /dev/null +++ b/app/Http/Controllers/Api/Scores/HomeworkController.php @@ -0,0 +1,85 @@ +validated(); + $data = $this->service->list( + (int) $payload['class_section_id'], + $payload['semester'] ?? null, + $payload['school_year'] ?? null + ); + + return response()->json([ + 'ok' => true, + 'students' => ScoreStudentResource::collection($data['students']), + 'headers' => $data['headers'], + 'semester' => $data['semester'], + 'school_year' => $data['schoolYear'], + 'scores_locked' => $data['scoresLocked'], + 'missing_ok_map' => $data['missingOkMap'], + ]); + } + + public function update(ScoreUpdateRequest $request): JsonResponse + { + $payload = $request->validated(); + $count = $this->service->update( + (int) $payload['class_section_id'], + (string) $payload['semester'], + (string) $payload['school_year'], + $payload['scores'], + $payload['missing_ok'] ?? [], + (int) (auth()->id() ?? 0) + ); + + $this->refreshSemesterScores((int) $payload['class_section_id'], (string) $payload['semester'], (string) $payload['school_year']); + + return response()->json(['ok' => true, 'updated' => $count]); + } + + public function addColumn(ScoreAddColumnRequest $request): JsonResponse + { + $payload = $request->validated(); + $nextIndex = $this->service->addColumn( + (int) $payload['class_section_id'], + (string) $payload['semester'], + (string) $payload['school_year'], + (int) (auth()->id() ?? 0) + ); + + return response()->json(['ok' => true, 'homework_index' => $nextIndex]); + } + + private function refreshSemesterScores(int $classSectionId, string $semester, string $schoolYear): void + { + $studentInfo = Student::getStudentInfoByClassSectionId($classSectionId, $semester, $schoolYear, (int) (auth()->id() ?? 0)); + if (empty($studentInfo)) { + return; + } + try { + $this->semesterScoreService->updateScoresForStudents($studentInfo, $semester, $schoolYear); + } catch (\Throwable $e) { + } + } +} diff --git a/app/Http/Controllers/Api/Scores/MidtermController.php b/app/Http/Controllers/Api/Scores/MidtermController.php new file mode 100644 index 00000000..25e8d335 --- /dev/null +++ b/app/Http/Controllers/Api/Scores/MidtermController.php @@ -0,0 +1,67 @@ +validated(); + $data = $this->service->list('midterm_exam', (int) $payload['class_section_id'], $payload['semester'] ?? null, $payload['school_year'] ?? null); + + return response()->json([ + 'ok' => true, + 'students' => ScoreStudentResource::collection($data['students']), + 'semester' => $data['semester'], + 'school_year' => $data['schoolYear'], + 'scores_locked' => $data['scoresLocked'], + 'missing_ok_map' => $data['missingOkMap'], + ]); + } + + public function update(ScoreUpdateRequest $request): JsonResponse + { + $payload = $request->validated(); + $count = $this->service->update( + 'midterm_exam', + (int) $payload['class_section_id'], + (string) $payload['semester'], + (string) $payload['school_year'], + $payload['scores'], + $payload['missing_ok'] ?? [], + (int) (auth()->id() ?? 0) + ); + + $this->refreshSemesterScores((int) $payload['class_section_id'], (string) $payload['semester'], (string) $payload['school_year']); + + return response()->json(['ok' => true, 'updated' => $count]); + } + + private function refreshSemesterScores(int $classSectionId, string $semester, string $schoolYear): void + { + $studentInfo = Student::getStudentInfoByClassSectionId($classSectionId, $semester, $schoolYear, (int) (auth()->id() ?? 0)); + if (empty($studentInfo)) { + return; + } + try { + $this->semesterScoreService->updateScoresForStudents($studentInfo, $semester, $schoolYear); + } catch (\Throwable $e) { + } + } +} diff --git a/app/Http/Controllers/Api/Scores/ParticipationController.php b/app/Http/Controllers/Api/Scores/ParticipationController.php new file mode 100644 index 00000000..62a468b9 --- /dev/null +++ b/app/Http/Controllers/Api/Scores/ParticipationController.php @@ -0,0 +1,70 @@ +validated(); + $data = $this->service->list( + (int) $payload['class_section_id'], + $payload['semester'] ?? null, + $payload['school_year'] ?? null + ); + + return response()->json([ + 'ok' => true, + 'students' => ScoreStudentResource::collection($data['students']), + 'semester' => $data['semester'], + 'school_year' => $data['schoolYear'], + 'scores_locked' => $data['scoresLocked'], + 'missing_ok_map' => $data['missingOkMap'], + ]); + } + + public function update(ScoreUpdateRequest $request): JsonResponse + { + $payload = $request->validated(); + $count = $this->service->update( + (int) $payload['class_section_id'], + (string) $payload['semester'], + (string) $payload['school_year'], + $payload['scores'], + $payload['missing_ok'] ?? [], + (int) (auth()->id() ?? 0) + ); + + $this->refreshSemesterScores((int) $payload['class_section_id'], (string) $payload['semester'], (string) $payload['school_year']); + + return response()->json(['ok' => true, 'updated' => $count]); + } + + private function refreshSemesterScores(int $classSectionId, string $semester, string $schoolYear): void + { + $studentInfo = Student::getStudentInfoByClassSectionId($classSectionId, $semester, $schoolYear, (int) (auth()->id() ?? 0)); + if (empty($studentInfo)) { + return; + } + try { + $this->semesterScoreService->updateScoresForStudents($studentInfo, $semester, $schoolYear); + } catch (\Throwable $e) { + } + } +} diff --git a/app/Http/Controllers/Api/Scores/ProjectController.php b/app/Http/Controllers/Api/Scores/ProjectController.php new file mode 100644 index 00000000..4116cd2e --- /dev/null +++ b/app/Http/Controllers/Api/Scores/ProjectController.php @@ -0,0 +1,86 @@ +validated(); + $data = $this->service->list( + (int) $payload['class_section_id'], + $payload['semester'] ?? null, + $payload['school_year'] ?? null + ); + + return response()->json([ + 'ok' => true, + 'students' => ScoreStudentResource::collection($data['students']), + 'headers' => $data['headers'], + 'semester' => $data['semester'], + 'school_year' => $data['schoolYear'], + 'scores_locked' => $data['scoresLocked'], + 'missing_ok_map' => $data['missingOkMap'], + ]); + } + + public function update(ScoreUpdateRequest $request): JsonResponse + { + $payload = $request->validated(); + $count = $this->service->update( + (int) $payload['class_section_id'], + (string) $payload['semester'], + (string) $payload['school_year'], + $payload['scores'], + $payload['missing_ok'] ?? [], + (int) (auth()->id() ?? 0) + ); + + $this->refreshSemesterScores((int) $payload['class_section_id'], (string) $payload['semester'], (string) $payload['school_year']); + + return response()->json(['ok' => true, 'updated' => $count]); + } + + public function addColumn(ScoreAddColumnRequest $request): JsonResponse + { + $payload = $request->validated(); + $nextIndex = $this->service->addColumn( + (int) $payload['class_section_id'], + (string) $payload['semester'], + (string) $payload['school_year'], + (int) (auth()->id() ?? 0) + ); + + return response()->json(['ok' => true, 'project_index' => $nextIndex]); + } + + private function refreshSemesterScores(int $classSectionId, string $semester, string $schoolYear): void + { + $studentInfo = Student::getStudentInfoByClassSectionId($classSectionId, $semester, $schoolYear, (int) (auth()->id() ?? 0)); + if (empty($studentInfo)) { + return; + } + try { + $this->semesterScoreService->updateScoresForStudents($studentInfo, $semester, $schoolYear); + } catch (\Throwable $e) { + // swallow errors to keep API response stable + } + } +} diff --git a/app/Http/Controllers/Api/Scores/QuizController.php b/app/Http/Controllers/Api/Scores/QuizController.php new file mode 100644 index 00000000..1eeed557 --- /dev/null +++ b/app/Http/Controllers/Api/Scores/QuizController.php @@ -0,0 +1,85 @@ +validated(); + $data = $this->service->list( + (int) $payload['class_section_id'], + $payload['semester'] ?? null, + $payload['school_year'] ?? null + ); + + return response()->json([ + 'ok' => true, + 'students' => ScoreStudentResource::collection($data['students']), + 'headers' => $data['headers'], + 'semester' => $data['semester'], + 'school_year' => $data['schoolYear'], + 'scores_locked' => $data['scoresLocked'], + 'missing_ok_map' => $data['missingOkMap'], + ]); + } + + public function update(ScoreUpdateRequest $request): JsonResponse + { + $payload = $request->validated(); + $count = $this->service->update( + (int) $payload['class_section_id'], + (string) $payload['semester'], + (string) $payload['school_year'], + $payload['scores'], + $payload['missing_ok'] ?? [], + (int) (auth()->id() ?? 0) + ); + + $this->refreshSemesterScores((int) $payload['class_section_id'], (string) $payload['semester'], (string) $payload['school_year']); + + return response()->json(['ok' => true, 'updated' => $count]); + } + + public function addColumn(ScoreAddColumnRequest $request): JsonResponse + { + $payload = $request->validated(); + $nextIndex = $this->service->addColumn( + (int) $payload['class_section_id'], + (string) $payload['semester'], + (string) $payload['school_year'], + (int) (auth()->id() ?? 0) + ); + + return response()->json(['ok' => true, 'quiz_index' => $nextIndex]); + } + + private function refreshSemesterScores(int $classSectionId, string $semester, string $schoolYear): void + { + $studentInfo = Student::getStudentInfoByClassSectionId($classSectionId, $semester, $schoolYear, (int) (auth()->id() ?? 0)); + if (empty($studentInfo)) { + return; + } + try { + $this->semesterScoreService->updateScoresForStudents($studentInfo, $semester, $schoolYear); + } catch (\Throwable $e) { + } + } +} diff --git a/app/Http/Controllers/Api/Scores/ScoreCommentController.php b/app/Http/Controllers/Api/Scores/ScoreCommentController.php new file mode 100644 index 00000000..b362381c --- /dev/null +++ b/app/Http/Controllers/Api/Scores/ScoreCommentController.php @@ -0,0 +1,88 @@ +validated(); + $data = $this->service->list( + $payload['class_section_id'] ?? null, + $payload['semester'] ?? null, + $payload['school_year'] ?? null + ); + + $studentRows = []; + foreach ($data['students'] as $student) { + $studentId = (int) ($student['student_id'] ?? 0); + $studentRows[] = [ + 'student_id' => $studentId, + 'firstname' => $student['firstname'] ?? null, + 'lastname' => $student['lastname'] ?? null, + 'school_id' => $student['school_id'] ?? null, + 'comments' => $data['comments'][$studentId] ?? [], + ]; + } + + return response()->json([ + 'ok' => true, + 'students' => ScoreCommentResource::collection($studentRows), + 'semester' => $data['semester'], + 'school_year' => $data['schoolYear'], + 'class_section_id' => $data['classSectionId'], + 'scores_locked' => $data['scoresLocked'], + ]); + } + + public function store(ScoreCommentSaveRequest $request): JsonResponse + { + $payload = $request->validated(); + $result = $this->service->save( + (int) $payload['class_section_id'], + (string) $payload['semester'], + (string) $payload['school_year'], + $payload['comments'], + $payload['missing_ok'] ?? [], + (int) (auth()->id() ?? 0) + ); + + if (!empty($result['errors'])) { + return response()->json(['ok' => false, 'errors' => $result['errors']], 422); + } + + return response()->json(['ok' => true]); + } + + public function update(ScoreCommentUpdateRequest $request): JsonResponse + { + $payload = $request->validated(); + $result = $this->service->update( + (int) $payload['class_section_id'], + (string) $payload['semester'], + (string) $payload['school_year'], + $payload['comments'] ?? [], + $payload['reviews'] ?? [], + (int) (auth()->id() ?? 0) + ); + + if (!empty($result['errors'])) { + return response()->json(['ok' => false, 'errors' => $result['errors']], 422); + } + + return response()->json(['ok' => true]); + } +} diff --git a/app/Http/Controllers/Api/Scores/ScoreController.php b/app/Http/Controllers/Api/Scores/ScoreController.php new file mode 100644 index 00000000..d801db54 --- /dev/null +++ b/app/Http/Controllers/Api/Scores/ScoreController.php @@ -0,0 +1,66 @@ +validated(); + $teacherId = (int) (auth()->id() ?? 0); + + $data = $this->service->overview( + $teacherId, + isset($payload['class_section_id']) ? (int) $payload['class_section_id'] : null, + $payload['semester'] ?? null, + $payload['school_year'] ?? null + ); + + return response()->json([ + 'ok' => true, + 'students' => ScoreStudentResource::collection($data['students'] ?? []), + 'class_section_id' => $data['class_section_id'] ?? null, + 'class_section_name' => $data['class_section_name'] ?? null, + 'semester' => $data['semester'] ?? null, + 'school_year' => $data['school_year'] ?? null, + 'scores_locked' => $data['scoresLocked'] ?? false, + ]); + } + + public function lock(ScoreLockRequest $request): JsonResponse + { + $payload = $request->validated(); + $this->service->submitScoresLock( + (int) $payload['class_section_id'], + (string) $payload['semester'], + (string) $payload['school_year'], + $payload['missing_ok'] ?? [], + (int) (auth()->id() ?? 0) + ); + + return response()->json(['ok' => true]); + } + + public function studentScores(ScoreStudentRequest $request): JsonResponse + { + $payload = $request->validated(); + $parentId = (int) (auth()->id() ?? 0); + + $data = $this->service->viewStudentScores($parentId, (string) $payload['school_year']); + + return response()->json(['ok' => true] + $data); + } +} diff --git a/app/Http/Controllers/Api/Scores/ScorePredictorController.php b/app/Http/Controllers/Api/Scores/ScorePredictorController.php new file mode 100644 index 00000000..1f69533e --- /dev/null +++ b/app/Http/Controllers/Api/Scores/ScorePredictorController.php @@ -0,0 +1,35 @@ +validated(); + $data = $this->service->report( + $payload['school_year'] ?? null, + isset($payload['class_section_id']) ? (int) $payload['class_section_id'] : null + ); + + return response()->json([ + 'ok' => true, + 'students' => ScorePredictorStudentResource::collection($data['students'] ?? []), + 'school_year' => $data['school_year'] ?? null, + 'class_sections' => $data['class_sections'] ?? [], + 'selected_class_section_id' => $data['selected_class_section_id'] ?? null, + 'semester' => $data['semester'] ?? null, + ]); + } +} diff --git a/app/Http/Controllers/Api/System/SemesterRangeController.php b/app/Http/Controllers/Api/System/SemesterRangeController.php new file mode 100644 index 00000000..eca196d3 --- /dev/null +++ b/app/Http/Controllers/Api/System/SemesterRangeController.php @@ -0,0 +1,80 @@ +validated(); + [$start, $end] = $this->service->getSchoolYearRange($payload['school_year']); + + return response()->json([ + 'ok' => true, + 'school_year' => $payload['school_year'], + 'range' => new SemesterRangeResource([ + 'start_date' => $start, + 'end_date' => $end, + ]), + ]); + } + + public function semesterRange(SemesterRangeRequest $request): JsonResponse + { + $payload = $request->validated(); + $range = $this->service->getSemesterRange($payload['school_year'], $payload['semester']); + + if ($range === null) { + return response()->json(['ok' => false, 'message' => 'Invalid semester range.'], 422); + } + + return response()->json([ + 'ok' => true, + 'school_year' => $payload['school_year'], + 'semester' => $this->service->normalizeSemester($payload['semester']), + 'range' => new SemesterRangeResource([ + 'start_date' => $range[0], + 'end_date' => $range[1], + ]), + ]); + } + + public function normalize(SemesterNormalizeRequest $request): JsonResponse + { + $payload = $request->validated(); + + return response()->json([ + 'ok' => true, + 'normalized' => $this->service->normalizeSemester($payload['semester']), + ]); + } + + public function resolve(SemesterResolveRequest $request): JsonResponse + { + $payload = $request->validated(); + $semester = $this->service->getSemesterForDate($payload['date'] ?? null); + + return response()->json([ + 'ok' => true, + 'resolution' => new SemesterResolveResource([ + 'date' => $payload['date'] ?? null, + 'semester' => $semester, + ]), + ]); + } +} diff --git a/app/Http/Controllers/Api/UserController.php b/app/Http/Controllers/Api/UserController.php new file mode 100644 index 00000000..8ccd2c6c --- /dev/null +++ b/app/Http/Controllers/Api/UserController.php @@ -0,0 +1,102 @@ +validated(); + $users = $this->listService->list($payload['sort'] ?? null, $payload['order'] ?? null); + + return response()->json([ + 'ok' => true, + 'users' => UserWithRolesResource::collection($users), + ]); + } + + public function store(UserStoreRequest $request): JsonResponse + { + $payload = $request->validated(); + $result = $this->managementService->create($payload, (int) (auth()->id() ?? 0)); + + if (empty($result['ok'])) { + return response()->json([ + 'ok' => false, + 'message' => $result['message'] ?? 'Failed to create user.', + ], 422); + } + + return response()->json([ + 'ok' => true, + 'user_id' => $result['user']->id ?? null, + ], 201); + } + + public function update(UserUpdateRequest $request, int $userId): JsonResponse + { + $payload = $request->validated(); + $result = $this->managementService->update($userId, $payload); + + if (empty($result['ok'])) { + return response()->json([ + 'ok' => false, + 'message' => $result['message'] ?? 'Failed to update user.', + ], 422); + } + + return response()->json([ + 'ok' => true, + ]); + } + + public function destroy(int $userId): JsonResponse + { + $result = $this->managementService->delete($userId); + + if (empty($result['ok'])) { + return response()->json([ + 'ok' => false, + 'message' => $result['message'] ?? 'Failed to delete user.', + ], 404); + } + + return response()->json([ + 'ok' => true, + ]); + } + + public function loginActivity(LoginActivityRequest $request): JsonResponse + { + $payload = $request->validated(); + $result = $this->loginActivityService->list( + (int) ($payload['per_page'] ?? 25), + (int) ($payload['page'] ?? 1) + ); + + return response()->json([ + 'ok' => true, + 'activities' => LoginActivityResource::collection($result['activities']), + 'pagination' => $result['pagination'], + ]); + } +} diff --git a/app/Http/Controllers/old/ApiClient.php b/app/Http/Controllers/old/ApiClient.php new file mode 100644 index 00000000..8dc3ae8a --- /dev/null +++ b/app/Http/Controllers/old/ApiClient.php @@ -0,0 +1,107 @@ +http = $http; + $this->config = $config; + } + + public function get(string $uri, array $query = [], array $headers = []): array + { + return $this->request('get', $uri, [ + 'headers' => $this->mergeHeaders($headers), + 'query' => $query, + ]); + } + + public function post(string $uri, array $data = [], array $headers = []): array + { + return $this->request('post', $uri, [ + 'headers' => $this->mergeHeaders($headers, ['Content-Type' => 'application/json']), + 'json' => $data, + ]); + } + + public function put(string $uri, array $data = [], array $headers = []): array + { + return $this->request('put', $uri, [ + 'headers' => $this->mergeHeaders($headers, ['Content-Type' => 'application/json']), + 'json' => $data, + ]); + } + + public function delete(string $uri, array $data = [], array $headers = []): array + { + return $this->request('delete', $uri, [ + 'headers' => $this->mergeHeaders($headers, ['Content-Type' => 'application/json']), + 'json' => $data, + ]); + } + + /** + * Low-level access for uncommon HTTP methods or options + */ + public function request(string $method, string $uri, array $options = []): array + { + $url = $this->fullUrl($uri); + $options['timeout'] = $options['timeout'] ?? $this->config->timeout; + + $response = $this->http->request(strtoupper($method), $url, $options); + + $status = $response->getStatusCode(); + $body = (string) $response->getBody(); + + $decoded = null; + if ($this->isJson($body)) { + $decoded = json_decode($body, true); + } + + return [ + 'ok' => $status >= 200 && $status < 300, + 'status' => $status, + 'headers' => $response->getHeaderLine('Content-Type'), + 'data' => $decoded, + 'raw' => $decoded === null ? $body : null, + ]; + } + + protected function fullUrl(string $uri): string + { + if (preg_match('#^https?://#i', $uri)) { + return $uri; + } + + $base = rtrim($this->config->baseURL ?? '', '/'); + if ($base === '') { + return '/' . ltrim($uri, '/'); + } + return $base . '/' . ltrim($uri, '/'); + } + + protected function mergeHeaders(array $override = [], array $extra = []): array + { + $base = $this->config->defaultHeaders ?? []; + return array_filter(array_merge($base, $extra, $override), static function ($v) { + return $v !== null && $v !== ''; + }); + } + + protected function isJson(string $string): bool + { + json_decode($string); + return json_last_error() === JSON_ERROR_NONE; + } +} + +?> + diff --git a/app/Http/Controllers/old/BroadcastEmailController.php b/app/Http/Controllers/old/BroadcastEmailController.php deleted file mode 100644 index bdeab9d7..00000000 --- a/app/Http/Controllers/old/BroadcastEmailController.php +++ /dev/null @@ -1,256 +0,0 @@ -userModel = new UserModel(); - $this->mailer = new EmailService(); // use your existing mailer unmodified - } - - public function index() - { - helper(['form']); - - // Parents via your role-based function - $parents = $this->userModel->getParents(); - $parents = array_values(array_filter($parents, static fn($p) => !empty($p['email']))); - - // Sender list from MAIL_SENDERS directly (no change to EmailService) - $fromOptions = $this->senderOptionsFromEnv(); - - return view('administrator/broadcast_email', [ - 'parents' => $parents, - 'fromOptions' => $fromOptions, // [['key'=>'general','label'=>'Al Rahma Office '], ...] - ]); - } - - public function send() - { - helper(['form']); - - if (strtolower($this->request->getMethod()) !== 'post') { - return redirect()->to(site_url('admin/broadcast-email')); - } - - $isTestOnly = $this->request->getPost('send_test_only') !== null; - - $mode = (string) $this->request->getPost('mode'); // 'personalized' | 'standard' - $subject = trim((string) $this->request->getPost('subject')); - $fromKey = trim((string) $this->request->getPost('from_key') ?: 'general'); - $body = (string) $this->request->getPost('body_html'); - $body = $this->sanitizeEmailHtml($body); - - - // Layout options - $wrapLayout = (bool) $this->request->getPost('wrap_layout'); - $preheader = (string) ($this->request->getPost('preheader') ?? ''); - $ctaText = (string) ($this->request->getPost('cta_text') ?? ''); - $ctaUrl = (string) ($this->request->getPost('cta_url') ?? ''); - - $testEmail = trim((string) $this->request->getPost('test_email')); - - if ($subject === '' || $body === '') { - return redirect()->back()->withInput()->with('error', 'Subject and Body are required.'); - } - - $isPersonalized = ($mode === 'personalized'); - - // --- TEST ONLY --- - if ($isTestOnly) { - if ($testEmail === '') { - return redirect()->back()->withInput()->with('error', 'Provide a test email address.'); - } - - $recipientName = 'Parent'; - $html = $this->composeEmailHtml( - $wrapLayout, - $subject, - $body, - $recipientName, - $preheader, - $ctaText, - $ctaUrl, - $isPersonalized - ); - - $ok = $this->mailer->send($testEmail, '[TEST] ' . $subject, $html, $fromKey); - return redirect()->back()->with( - $ok ? 'message' : 'error', - $ok ? "Test email sent to {$testEmail}." : "Test email failed (mailer->send() returned false). Check logs." - ); - } - - // --- BROADCAST --- - $rawIds = (array) ($this->request->getPost('parent_ids') ?? []); - $ids = []; - foreach ($rawIds as $v) { - if (is_string($v) && strpos($v, ',') !== false) { - $ids = array_merge($ids, array_map('intval', explode(',', $v))); - } else { - $ids[] = (int) $v; - } - } - $ids = array_values(array_unique(array_filter($ids))); - - if (empty($ids)) { - return redirect()->back()->withInput()->with('error', 'Please select at least one parent.'); - } - - $rows = model(\App\Models\UserModel::class) - ->select('users.id, users.email, CONCAT(users.firstname, " ", users.lastname) AS name') - ->whereIn('users.id', $ids) - ->where('users.email IS NOT NULL AND users.email != ""') - ->findAll(); - - if (empty($rows)) { - return redirect()->back()->withInput()->with('error', 'No valid parent emails found.'); - } - - $stats = ['attempted' => 0, 'sent' => 0, 'failed' => 0, 'mode' => $mode]; - - foreach ($rows as $r) { - $stats['attempted']++; - $recipientName = $r['name'] ?: 'Parent'; - - $html = $this->composeEmailHtml( - $wrapLayout, - $subject, - $body, - $recipientName, - $preheader, - $ctaText, - $ctaUrl, - $isPersonalized - ); - - $ok = $this->mailer->send($r['email'], $subject, $html, $fromKey); - $ok ? $stats['sent']++ : $stats['failed']++; - } - - $msg = "Broadcast finished. Mode: {$stats['mode']}. Sent: {$stats['sent']}/{$stats['attempted']}. Failures: {$stats['failed']}."; - return redirect()->to(site_url('admin/broadcast-email')) - ->with($stats['failed'] > 0 ? 'error' : 'message', $msg); - } - - - /** - * Build HTML: optionally wrap $body inside your view('layout/email_layout', ...). - */ - private function composeEmailHtml( - bool $wrap, - string $subject, - string $body, - string $recipientName, - string $preheader = '', - string $ctaText = '', - string $ctaUrl = '', - bool $doPersonalize = true - ): string { - $content = $doPersonalize ? str_replace('{{name}}', $recipientName, $body) : $body; - - if (!$wrap) { - return $content; // raw body - } - - // Render a CHILD view that defines the section your layout expects - return view('emails/broadcast_wrapper', [ - 'subject' => $subject, - 'content' => $content, - // pass more if you later wire them in your layout - 'preheader' => $preheader, - 'cta_text' => $ctaText, - 'cta_url' => $ctaUrl, - ]); - } - - - private function sanitizeEmailHtml(string $html): string - { - // Remove '; + + $clean = $service->sanitizeHtml($html); + + $this->assertStringNotContainsString('script', $clean); + $this->assertStringNotContainsString('onclick', $clean); + } + + public function test_compose_replaces_name_when_personalized(): void + { + $service = new BroadcastEmailComposerService(); + $html = '

Hello {{name}}

'; + + $rendered = $service->compose(false, 'Subject', $html, 'Parent', '', '', '', true); + + $this->assertSame('

Hello Parent

', $rendered); + } +} diff --git a/tests/Unit/Services/BroadcastEmail/BroadcastEmailRecipientServiceTest.php b/tests/Unit/Services/BroadcastEmail/BroadcastEmailRecipientServiceTest.php new file mode 100644 index 00000000..2465c4db --- /dev/null +++ b/tests/Unit/Services/BroadcastEmail/BroadcastEmailRecipientServiceTest.php @@ -0,0 +1,93 @@ +seedParentData(); + + $service = new BroadcastEmailRecipientService(); + $parents = $service->parentsWithEmails(); + + $this->assertCount(1, $parents); + $this->assertSame('parent@example.com', $parents[0]['email']); + } + + public function test_recipients_by_ids_returns_names(): void + { + $this->seedParentData(); + + $service = new BroadcastEmailRecipientService(); + $recipients = $service->recipientsByIds([10]); + + $this->assertCount(1, $recipients); + $this->assertSame('Parent User', $recipients[0]['name']); + } + + private function seedParentData(): void + { + DB::table('roles')->insert([ + 'id' => 1, + 'name' => 'parent', + 'slug' => 'parent', + 'is_active' => 1, + ]); + + DB::table('users')->insert([ + [ + 'id' => 10, + 'school_id' => 1, + 'firstname' => 'Parent', + 'lastname' => 'User', + 'cellphone' => '5555555555', + 'email' => 'parent@example.com', + 'address_street' => '123 Main', + 'city' => 'City', + 'state' => 'ST', + 'zip' => '12345', + 'accept_school_policy' => 1, + 'is_verified' => 1, + 'status' => 'Active', + 'is_suspended' => 0, + 'failed_attempts' => 0, + 'password' => bcrypt('secret'), + 'semester' => 'Fall', + 'school_year' => '2025-2026', + ], + [ + 'id' => 11, + 'school_id' => 1, + 'firstname' => 'No', + 'lastname' => 'Email', + 'cellphone' => '5555555555', + 'email' => '', + 'address_street' => '123 Main', + 'city' => 'City', + 'state' => 'ST', + 'zip' => '12345', + 'accept_school_policy' => 1, + 'is_verified' => 1, + 'status' => 'Active', + 'is_suspended' => 0, + 'failed_attempts' => 0, + 'password' => bcrypt('secret'), + 'semester' => 'Fall', + 'school_year' => '2025-2026', + ], + ]); + + DB::table('user_roles')->insert([ + ['user_id' => 10, 'role_id' => 1], + ['user_id' => 11, 'role_id' => 1], + ]); + } +} diff --git a/tests/Unit/Services/BroadcastEmail/BroadcastEmailSenderOptionsServiceTest.php b/tests/Unit/Services/BroadcastEmail/BroadcastEmailSenderOptionsServiceTest.php new file mode 100644 index 00000000..7627c1ea --- /dev/null +++ b/tests/Unit/Services/BroadcastEmail/BroadcastEmailSenderOptionsServiceTest.php @@ -0,0 +1,23 @@ +listOptions(); + + $this->assertSame('general', $options[0]['key']); + $this->assertSame('Office ', $options[0]['label']); + + putenv('MAIL_SENDERS=' . $old); + } +} diff --git a/tests/Unit/Services/ClassPrep/ClassRosterServiceTest.php b/tests/Unit/Services/ClassPrep/ClassRosterServiceTest.php new file mode 100644 index 00000000..fea21717 --- /dev/null +++ b/tests/Unit/Services/ClassPrep/ClassRosterServiceTest.php @@ -0,0 +1,71 @@ +seedRosterData(); + + $service = new ClassRosterService(); + $students = $service->listStudentsByClass(101, '2025-2026'); + + $this->assertCount(1, $students); + $this->assertSame('1-A', $students[0]['registration_grade']); + } + + private function seedRosterData(): void + { + DB::table('configuration')->insert([ + ['id' => 1, 'config_key' => 'school_year', 'config_value' => '2025-2026'], + ]); + + DB::table('classes')->insert([ + 'id' => 1, + 'class_name' => 'Class 1', + 'schedule' => 'Sun', + 'capacity' => 20, + 'semester' => 'Fall', + 'school_year' => '2025-2026', + ]); + + DB::table('classSection')->insert([ + 'id' => 1, + 'class_id' => 1, + 'class_section_id' => 101, + 'class_section_name' => '1-A', + 'semester' => 'Fall', + 'school_year' => '2025-2026', + ]); + + DB::table('students')->insert([ + 'school_id' => 'S-1', + 'firstname' => 'Student', + 'lastname' => 'One', + 'age' => 8, + 'gender' => 'Male', + 'photo_consent' => 1, + 'parent_id' => 1, + 'year_of_registration' => '2025', + 'school_year' => '2025-2026', + 'semester' => 'Fall', + 'is_active' => 1, + 'is_new' => 0, + ]); + + DB::table('student_class')->insert([ + 'student_id' => 1, + 'class_section_id' => 101, + 'semester' => 'Fall', + 'school_year' => '2025-2026', + ]); + } +} diff --git a/tests/Unit/Services/ClassPrep/StickerCountServiceTest.php b/tests/Unit/Services/ClassPrep/StickerCountServiceTest.php new file mode 100644 index 00000000..e68a646a --- /dev/null +++ b/tests/Unit/Services/ClassPrep/StickerCountServiceTest.php @@ -0,0 +1,165 @@ +seedStickerData(); + + $service = new StickerCountService(); + $payload = $service->listAll('2025-2026', 'Fall'); + + $this->assertSame(2, $payload['totals']['students']); + $this->assertSame(6, $payload['totals']['primary']); + $this->assertSame(1, $payload['totals']['secondary']); + } + + public function test_list_for_class_limits_results(): void + { + $this->seedStickerData(); + + $service = new StickerCountService(); + $payload = $service->listForClass('2025-2026', 'Fall', 101); + + $this->assertSame(1, $payload['totals']['students']); + $this->assertSame(3, $payload['totals']['primary']); + $this->assertSame(0, $payload['totals']['secondary']); + } + + private function seedStickerData(): void + { + DB::table('configuration')->insert([ + ['id' => 1, 'config_key' => 'school_year', 'config_value' => '2025-2026'], + ['id' => 2, 'config_key' => 'semester', 'config_value' => 'Fall'], + ]); + + DB::table('classes')->insert([ + [ + 'id' => 1, + 'class_name' => 'Class 1', + 'schedule' => 'Sun', + 'capacity' => 20, + 'semester' => 'Fall', + 'school_year' => '2025-2026', + ], + [ + 'id' => 2, + 'class_name' => 'Class 2', + 'schedule' => 'Sun', + 'capacity' => 20, + 'semester' => 'Fall', + 'school_year' => '2025-2026', + ], + [ + 'id' => 3, + 'class_name' => 'Class 3', + 'schedule' => 'Sun', + 'capacity' => 20, + 'semester' => 'Fall', + 'school_year' => '2025-2026', + ], + ]); + + DB::table('classSection')->insert([ + [ + 'id' => 1, + 'class_id' => 1, + 'class_section_id' => 101, + 'class_section_name' => '1-A', + 'semester' => 'Fall', + 'school_year' => '2025-2026', + ], + [ + 'id' => 2, + 'class_id' => 2, + 'class_section_id' => 102, + 'class_section_name' => '5-B', + 'semester' => 'Fall', + 'school_year' => '2025-2026', + ], + [ + 'id' => 3, + 'class_id' => 3, + 'class_section_id' => 103, + 'class_section_name' => 'Youth-1', + 'semester' => 'Fall', + 'school_year' => '2025-2026', + ], + ]); + + DB::table('students')->insert([ + [ + 'school_id' => 'S-1', + 'firstname' => 'Student', + 'lastname' => 'One', + 'age' => 8, + 'gender' => 'Male', + 'photo_consent' => 1, + 'parent_id' => 1, + 'year_of_registration' => '2025', + 'school_year' => '2025-2026', + 'semester' => 'Fall', + 'is_active' => 1, + 'is_new' => 0, + ], + [ + 'school_id' => 'S-2', + 'firstname' => 'Student', + 'lastname' => 'Two', + 'age' => 10, + 'gender' => 'Female', + 'photo_consent' => 1, + 'parent_id' => 1, + 'year_of_registration' => '2025', + 'school_year' => '2025-2026', + 'semester' => 'Fall', + 'is_active' => 1, + 'is_new' => 0, + ], + [ + 'school_id' => 'S-3', + 'firstname' => 'Student', + 'lastname' => 'Three', + 'age' => 12, + 'gender' => 'Female', + 'photo_consent' => 1, + 'parent_id' => 1, + 'year_of_registration' => '2025', + 'school_year' => '2025-2026', + 'semester' => 'Fall', + 'is_active' => 1, + 'is_new' => 1, + ], + ]); + + DB::table('student_class')->insert([ + [ + 'student_id' => 1, + 'class_section_id' => 101, + 'semester' => 'Fall', + 'school_year' => '2025-2026', + ], + [ + 'student_id' => 2, + 'class_section_id' => 102, + 'semester' => 'Fall', + 'school_year' => '2025-2026', + ], + [ + 'student_id' => 3, + 'class_section_id' => 103, + 'semester' => 'Fall', + 'school_year' => '2025-2026', + ], + ]); + } +} diff --git a/tests/Unit/Services/ClassPreparation/ClassPreparationAdjustmentServiceTest.php b/tests/Unit/Services/ClassPreparation/ClassPreparationAdjustmentServiceTest.php new file mode 100644 index 00000000..8cc02f54 --- /dev/null +++ b/tests/Unit/Services/ClassPreparation/ClassPreparationAdjustmentServiceTest.php @@ -0,0 +1,30 @@ +insert([ + 'class_section_id' => 101, + 'item_name' => 'Small Table', + 'adjustment' => 2, + 'school_year' => '2025-2026', + 'adjustable' => 1, + ]); + + $service = new ClassPreparationAdjustmentService(); + [$items, $adjMap] = $service->applyAdjustments(['Small Table' => 1], '101', '2025-2026'); + + $this->assertSame(3, $items['Small Table']); + $this->assertSame(2, $adjMap['Small Table']); + } +} diff --git a/tests/Unit/Services/ClassPreparation/ClassPreparationCalculatorServiceTest.php b/tests/Unit/Services/ClassPreparation/ClassPreparationCalculatorServiceTest.php new file mode 100644 index 00000000..c3096037 --- /dev/null +++ b/tests/Unit/Services/ClassPreparation/ClassPreparationCalculatorServiceTest.php @@ -0,0 +1,59 @@ +insert([ + 'id' => 1, + 'class_id' => 1, + 'class_section_id' => 101, + 'class_section_name' => '2-A', + 'semester' => 'Fall', + 'school_year' => '2025-2026', + ]); + + $service = new ClassPreparationCalculatorService(); + + $this->assertSame(2, $service->getClassLevelBySection('101')); + $this->assertSame(1, $service->getClassLevelBySection('KG')); + } + + public function test_calculate_prep_items_for_lower_grades(): void + { + DB::table('configuration')->insert([ + ['id' => 1, 'config_key' => 'school_year', 'config_value' => '2025-2026'], + ]); + + DB::table('inventory_categories')->insert([ + ['name' => 'Small Table', 'grade_min' => null, 'grade_max' => null, 'type' => 'classroom'], + ['name' => 'Small Chair', 'grade_min' => null, 'grade_max' => null, 'type' => 'classroom'], + ['name' => 'Teacher Chair', 'grade_min' => null, 'grade_max' => null, 'type' => 'classroom'], + ['name' => 'Grade Box', 'grade_min' => null, 'grade_max' => null, 'type' => 'classroom'], + ]); + + DB::table('teacher_class')->insert([ + 'class_section_id' => 101, + 'teacher_id' => 1, + 'position' => 'main', + 'school_year' => '2025-2026', + ]); + + $service = new ClassPreparationCalculatorService(); + $items = $service->calculatePrepItems(6, 1, '101'); + + $this->assertSame(2, $items['Small Table']); + $this->assertSame(6, $items['Small Chair']); + $this->assertSame(1, $items['Teacher Chair']); + $this->assertSame(1, $items['Grade Box']); + } +} diff --git a/tests/Unit/Services/ClassPreparation/ClassPreparationContextServiceTest.php b/tests/Unit/Services/ClassPreparation/ClassPreparationContextServiceTest.php new file mode 100644 index 00000000..6da0be6f --- /dev/null +++ b/tests/Unit/Services/ClassPreparation/ClassPreparationContextServiceTest.php @@ -0,0 +1,28 @@ +insert([ + 'student_id' => 1, + 'class_section_id' => 101, + 'semester' => 'Fall', + 'school_year' => '2025-2026', + ]); + + $service = new ClassPreparationContextService(); + + $this->assertTrue($service->hasRosterForSemester('2025-2026', 'Fall')); + $this->assertFalse($service->hasRosterForSemester('2025-2026', 'Spring')); + } +} diff --git a/tests/Unit/Services/ClassPreparation/ClassPreparationInventoryServiceTest.php b/tests/Unit/Services/ClassPreparation/ClassPreparationInventoryServiceTest.php new file mode 100644 index 00000000..c6b9af3b --- /dev/null +++ b/tests/Unit/Services/ClassPreparation/ClassPreparationInventoryServiceTest.php @@ -0,0 +1,48 @@ +insert([ + 'id' => 1, + 'type' => 'classroom', + 'name' => 'Small Table', + ]); + + DB::table('inventory_items')->insert([ + [ + 'type' => 'classroom', + 'category_id' => 1, + 'name' => 'Small Table', + 'quantity' => 5, + 'good_qty' => 5, + 'school_year' => '2025-2026', + 'semester' => 'Fall', + ], + [ + 'type' => 'classroom', + 'category_id' => 1, + 'name' => 'Small Table', + 'quantity' => 3, + 'good_qty' => 3, + 'school_year' => '2025-2026', + 'semester' => 'Fall', + ], + ]); + + $service = new ClassPreparationInventoryService(); + $map = $service->buildAvailability('2025-2026', 'Fall', true, ['Small Table']); + + $this->assertSame(8, $map['Small Table']); + } +} diff --git a/tests/Unit/Services/ClassPreparation/ClassPreparationLogServiceTest.php b/tests/Unit/Services/ClassPreparation/ClassPreparationLogServiceTest.php new file mode 100644 index 00000000..158868d5 --- /dev/null +++ b/tests/Unit/Services/ClassPreparation/ClassPreparationLogServiceTest.php @@ -0,0 +1,36 @@ +assertTrue($service->hasPrepChanged(['a' => 1], ['a' => 2])); + $this->assertFalse($service->hasPrepChanged(['a' => 1], ['a' => 1])); + } + + public function test_create_log_and_get_latest(): void + { + $service = new ClassPreparationLogService(); + $created = $service->createLog('101', '1-A', '2025-2026', ['Small Table' => 2], '2025-09-01 00:00:00'); + + $this->assertTrue($created); + $this->assertDatabaseHas('class_preparation_log', [ + 'class_section_id' => 101, + 'school_year' => '2025-2026', + ]); + + $log = $service->getLatestLog('101', '2025-2026'); + $this->assertNotNull($log); + } +} diff --git a/tests/Unit/Services/ClassPreparation/ClassPreparationRosterServiceTest.php b/tests/Unit/Services/ClassPreparation/ClassPreparationRosterServiceTest.php new file mode 100644 index 00000000..c3c1db0a --- /dev/null +++ b/tests/Unit/Services/ClassPreparation/ClassPreparationRosterServiceTest.php @@ -0,0 +1,84 @@ +seedRoster(); + + $service = new ClassPreparationRosterService(); + $rows = $service->getClassSectionStudentCounts('2025-2026', 'Fall', true); + + $this->assertCount(1, $rows); + $this->assertSame(101, (int) $rows[0]->class_section_id); + $this->assertSame(2, (int) $rows[0]->student_count); + } + + public function test_get_student_count_for_section(): void + { + $this->seedRoster(); + + $service = new ClassPreparationRosterService(); + $count = $service->getStudentCountForSection('2025-2026', 'Fall', true, '101'); + + $this->assertSame(2, $count); + } + + private function seedRoster(): void + { + DB::table('students')->insert([ + [ + 'id' => 1, + 'school_id' => 'S-1', + 'firstname' => 'Student', + 'lastname' => 'One', + 'age' => 8, + 'gender' => 'Male', + 'photo_consent' => 1, + 'parent_id' => 1, + 'year_of_registration' => '2025', + 'school_year' => '2025-2026', + 'semester' => 'Fall', + 'is_active' => 1, + ], + [ + 'id' => 2, + 'school_id' => 'S-2', + 'firstname' => 'Student', + 'lastname' => 'Two', + 'age' => 9, + 'gender' => 'Female', + 'photo_consent' => 1, + 'parent_id' => 1, + 'year_of_registration' => '2025', + 'school_year' => '2025-2026', + 'semester' => 'Fall', + 'is_active' => 1, + ], + ]); + + DB::table('student_class')->insert([ + [ + 'student_id' => 1, + 'class_section_id' => 101, + 'semester' => 'Fall', + 'school_year' => '2025-2026', + ], + [ + 'student_id' => 2, + 'class_section_id' => 101, + 'semester' => 'Fall', + 'school_year' => '2025-2026', + ], + ]); + } +} diff --git a/tests/Unit/Services/ClassPreparation/ClassPreparationServiceTest.php b/tests/Unit/Services/ClassPreparation/ClassPreparationServiceTest.php new file mode 100644 index 00000000..853f8209 --- /dev/null +++ b/tests/Unit/Services/ClassPreparation/ClassPreparationServiceTest.php @@ -0,0 +1,93 @@ +seedPrepData(); + + $service = new ClassPreparationService(); + $payload = $service->listPrep('2025-2026', 'Fall'); + + $this->assertSame('2025-2026', $payload['schoolYear']); + $this->assertSame('Fall', $payload['semester']); + $this->assertNotEmpty($payload['results']); + $this->assertArrayHasKey('Small Table', $payload['totals']); + } + + public function test_mark_printed_creates_logs(): void + { + $this->seedPrepData(); + + $service = new ClassPreparationService(); + $count = $service->markPrinted('2025-2026', 'Fall', [101]); + + $this->assertSame(1, $count); + $this->assertDatabaseHas('class_preparation_log', [ + 'class_section_id' => 101, + 'school_year' => '2025-2026', + ]); + } + + private function seedPrepData(): void + { + DB::table('configuration')->insert([ + ['id' => 1, 'config_key' => 'school_year', 'config_value' => '2025-2026'], + ['id' => 2, 'config_key' => 'semester', 'config_value' => 'Fall'], + ]); + + DB::table('classSection')->insert([ + 'id' => 1, + 'class_id' => 1, + 'class_section_id' => 101, + 'class_section_name' => '1-A', + 'semester' => 'Fall', + 'school_year' => '2025-2026', + ]); + + DB::table('students')->insert([ + 'school_id' => 'S-1', + 'firstname' => 'Student', + 'lastname' => 'One', + 'age' => 8, + 'gender' => 'Male', + 'photo_consent' => 1, + 'parent_id' => 1, + 'year_of_registration' => '2025', + 'school_year' => '2025-2026', + 'semester' => 'Fall', + 'is_active' => 1, + ]); + + DB::table('student_class')->insert([ + 'student_id' => 1, + 'class_section_id' => 101, + 'semester' => 'Fall', + 'school_year' => '2025-2026', + ]); + + DB::table('inventory_categories')->insert([ + 'type' => 'classroom', + 'name' => 'Small Table', + ]); + + DB::table('inventory_items')->insert([ + 'type' => 'classroom', + 'category_id' => 1, + 'name' => 'Small Table', + 'quantity' => 10, + 'good_qty' => 10, + 'school_year' => '2025-2026', + 'semester' => 'Fall', + ]); + } +} diff --git a/tests/Unit/Services/Communication/CommunicationTemplateServiceTest.php b/tests/Unit/Services/Communication/CommunicationTemplateServiceTest.php new file mode 100644 index 00000000..4c1fab86 --- /dev/null +++ b/tests/Unit/Services/Communication/CommunicationTemplateServiceTest.php @@ -0,0 +1,31 @@ +insert([ + 'id' => 1, + 'code' => 'welcome', + 'variant' => 'default', + 'subject' => 'Hello', + 'body_html' => 'Body', + 'is_active' => 1, + ]); + + $service = new CommunicationTemplateService(); + $templates = $service->listActiveTemplates(); + + $this->assertSame('welcome', $templates[0]['template_key']); + $this->assertSame('Body', $templates[0]['body']); + } +} diff --git a/tests/Unit/Services/CompetitionScores/CompetitionScoresSaveServiceTest.php b/tests/Unit/Services/CompetitionScores/CompetitionScoresSaveServiceTest.php new file mode 100644 index 00000000..cf550815 --- /dev/null +++ b/tests/Unit/Services/CompetitionScores/CompetitionScoresSaveServiceTest.php @@ -0,0 +1,46 @@ +filterScores([ + 1 => '10', + 2 => '4.5', + ]); + + $this->assertSame([1 => 10], $clean); + $this->assertSame([2], $invalid); + } + + public function test_save_scores_upserts(): void + { + DB::table('competition_scores')->insert([ + 'competition_id' => 1, + 'student_id' => 1, + 'class_section_id' => 101, + 'score' => 2, + ]); + + $service = new CompetitionScoresSaveService(); + $service->saveScores(1, 101, [1 => 7]); + + $this->assertDatabaseHas('competition_scores', [ + 'competition_id' => 1, + 'student_id' => 1, + 'class_section_id' => 101, + 'score' => 7, + ]); + } +} diff --git a/tests/Unit/Services/Discounts/DiscountApplyServiceTest.php b/tests/Unit/Services/Discounts/DiscountApplyServiceTest.php new file mode 100644 index 00000000..22db9a0c --- /dev/null +++ b/tests/Unit/Services/Discounts/DiscountApplyServiceTest.php @@ -0,0 +1,38 @@ +insert([ + ['id' => 1, 'config_key' => 'school_year', 'config_value' => '2025-2026'], + ['id' => 2, 'config_key' => 'semester', 'config_value' => 'Fall'], + ]); + + DB::table('discount_vouchers')->insert([ + 'id' => 1, + 'code' => 'USED-OUT', + 'discount_type' => 'fixed', + 'discount_value' => 10, + 'max_uses' => 1, + 'times_used' => 1, + 'is_active' => 1, + ]); + + $service = new DiscountApplyService(new DiscountContextService(), new DiscountInvoiceService()); + $result = $service->applyVoucher(1, [10], true, 1); + + $this->assertFalse($result['ok']); + } +} diff --git a/tests/Unit/Services/Expenses/ExpenseReceiptServiceTest.php b/tests/Unit/Services/Expenses/ExpenseReceiptServiceTest.php new file mode 100644 index 00000000..7a374401 --- /dev/null +++ b/tests/Unit/Services/Expenses/ExpenseReceiptServiceTest.php @@ -0,0 +1,26 @@ +create('receipt.pdf', 10, 'application/pdf'); + $filename = $service->storeReceipt($file); + + $this->assertNotEmpty($filename); + $this->assertSame(url('receipts/' . $filename), $service->receiptUrl($filename)); + } +} diff --git a/tests/Unit/Services/Expenses/ExpenseStaffServiceTest.php b/tests/Unit/Services/Expenses/ExpenseStaffServiceTest.php new file mode 100644 index 00000000..32874564 --- /dev/null +++ b/tests/Unit/Services/Expenses/ExpenseStaffServiceTest.php @@ -0,0 +1,75 @@ +insert([ + ['id' => 1, 'name' => 'administrator', 'slug' => 'administrator', 'is_active' => 1], + ['id' => 2, 'name' => 'parent', 'slug' => 'parent', 'is_active' => 1], + ]); + + DB::table('users')->insert([ + [ + 'id' => 10, + 'school_id' => 1, + 'firstname' => 'Staff', + 'lastname' => 'User', + 'cellphone' => '5555555555', + 'email' => 'staff@example.com', + 'address_street' => '123 Main', + 'city' => 'City', + 'state' => 'ST', + 'zip' => '12345', + 'accept_school_policy' => 1, + 'is_verified' => 1, + 'status' => 'Active', + 'is_suspended' => 0, + 'failed_attempts' => 0, + 'password' => bcrypt('secret'), + 'semester' => 'Fall', + 'school_year' => '2025-2026', + ], + [ + 'id' => 11, + 'school_id' => 1, + 'firstname' => 'Parent', + 'lastname' => 'User', + 'cellphone' => '5555555555', + 'email' => 'parent@example.com', + 'address_street' => '123 Main', + 'city' => 'City', + 'state' => 'ST', + 'zip' => '12345', + 'accept_school_policy' => 1, + 'is_verified' => 1, + 'status' => 'Active', + 'is_suspended' => 0, + 'failed_attempts' => 0, + 'password' => bcrypt('secret'), + 'semester' => 'Fall', + 'school_year' => '2025-2026', + ], + ]); + + DB::table('user_roles')->insert([ + ['user_id' => 10, 'role_id' => 1], + ['user_id' => 11, 'role_id' => 2], + ]); + + $service = new ExpenseStaffService(); + $staff = $service->listStaffUsers(); + + $this->assertCount(1, $staff); + $this->assertSame(10, $staff[0]['id']); + } +} diff --git a/tests/Unit/Services/ExtraCharges/ExtraChargesMetaServiceTest.php b/tests/Unit/Services/ExtraCharges/ExtraChargesMetaServiceTest.php new file mode 100644 index 00000000..e4b41d7d --- /dev/null +++ b/tests/Unit/Services/ExtraCharges/ExtraChargesMetaServiceTest.php @@ -0,0 +1,61 @@ +getSchoolYears('2025-2026'); + + $this->assertSame(['2025-2026', '2024-2025', '2023-2024', '2022-2023'], $years); + } + + public function test_get_school_years_merges_sources(): void + { + DB::table('additional_charges')->insert([ + [ + 'parent_id' => 1, + 'school_year' => '2025-2026', + 'semester' => 'Fall', + 'charge_type' => 'add', + 'title' => 'Test', + 'amount' => 1, + ], + [ + 'parent_id' => 1, + 'school_year' => '2024-2025', + 'semester' => 'Fall', + 'charge_type' => 'add', + 'title' => 'Test', + 'amount' => 1, + ], + ]); + + DB::table('invoices')->insert([ + [ + 'parent_id' => 1, + 'invoice_number' => 'INV-1', + 'total_amount' => 10, + 'balance' => 10, + 'paid_amount' => 0, + 'issue_date' => '2025-09-01', + 'status' => 'Unpaid', + 'school_year' => '2023-2024', + ], + ]); + + $service = new ExtraChargesMetaService(); + $years = $service->getSchoolYears(); + + $this->assertSame(['2025-2026', '2024-2025', '2023-2024'], $years); + } +} diff --git a/tests/Unit/Services/Files/ExamDraftDownloadNameServiceTest.php b/tests/Unit/Services/Files/ExamDraftDownloadNameServiceTest.php new file mode 100644 index 00000000..3ced4307 --- /dev/null +++ b/tests/Unit/Services/Files/ExamDraftDownloadNameServiceTest.php @@ -0,0 +1,43 @@ +insert([ + 'id' => 1, + 'class_section_id' => 1, + 'class_section_name' => '2B', + 'class_id' => 1, + 'semester' => 'Fall', + 'school_year' => '2025-2026', + ]); + + DB::table('exam_drafts')->insert([ + 'id' => 1, + 'teacher_id' => 1, + 'class_section_id' => 1, + 'semester' => 'Fall', + 'school_year' => '2025-2026', + 'exam_type' => 'Final Exam', + 'draft_title' => 'Draft', + 'teacher_file' => 'draft2.pdf', + 'version' => 3, + 'status' => 'draft', + ]); + + $service = new ExamDraftDownloadNameService(); + $name = $service->build('draft2.pdf', 'drafts'); + + $this->assertSame('2b_final_exam_v3', $name); + } +} diff --git a/tests/Unit/Services/Files/FileServeServiceTest.php b/tests/Unit/Services/Files/FileServeServiceTest.php new file mode 100644 index 00000000..b28f1297 --- /dev/null +++ b/tests/Unit/Services/Files/FileServeServiceTest.php @@ -0,0 +1,69 @@ +meta($dir, 'sample.pdf', ['pdf'], 'download'); + + $this->assertSame('sample.pdf', $meta['name']); + $this->assertSame('download.pdf', $meta['download_name']); + $this->assertSame(7, $meta['size']); + $this->assertNotEmpty($meta['etag']); + } + + public function test_serve_inline_returns_304_when_etag_matches(): void + { + $dir = storage_path('testing/files'); + if (!is_dir($dir)) { + mkdir($dir, 0777, true); + } + $path = $dir . DIRECTORY_SEPARATOR . 'cached.pdf'; + file_put_contents($path, 'PDFDATA'); + + $service = new FileServeService(); + $meta = $service->meta($dir, 'cached.pdf', ['pdf']); + + $request = Request::create('/files/cached.pdf', 'GET', [], [], [], [ + 'HTTP_IF_NONE_MATCH' => $meta['etag'], + ]); + + $response = $service->serveInline($dir, 'cached.pdf', ['pdf'], $request); + + $this->assertSame(304, $response->getStatusCode()); + $this->assertSame($meta['etag'], $response->headers->get('ETag')); + } + + public function test_serve_inline_rejects_invalid_extension(): void + { + $this->expectException(HttpException::class); + + $dir = storage_path('testing/files'); + if (!is_dir($dir)) { + mkdir($dir, 0777, true); + } + $path = $dir . DIRECTORY_SEPARATOR . 'sample.exe'; + file_put_contents($path, 'DATA'); + + $service = new FileServeService(); + $service->meta($dir, 'sample.exe', ['pdf']); + } +} diff --git a/tests/Unit/Services/Finance/FinancialChartServiceTest.php b/tests/Unit/Services/Finance/FinancialChartServiceTest.php new file mode 100644 index 00000000..e6b863ba --- /dev/null +++ b/tests/Unit/Services/Finance/FinancialChartServiceTest.php @@ -0,0 +1,31 @@ + 100, + 'amountCollected' => 80, + 'totalUnpaid' => 20, + 'totalDiscounts' => 5, + 'totalRefunds' => 2, + 'totalExpenses' => 10, + 'totalReimbursements' => 4, + 'netAmount' => 93, + ]; + + $this->assertNull($service->generateBarChart($summary)); + $this->assertNull($service->generatePieChart($summary)); + } +} diff --git a/tests/Unit/Services/Finance/FinancialPaymentServiceTest.php b/tests/Unit/Services/Finance/FinancialPaymentServiceTest.php new file mode 100644 index 00000000..491c43b2 --- /dev/null +++ b/tests/Unit/Services/Finance/FinancialPaymentServiceTest.php @@ -0,0 +1,94 @@ +insert([ + [ + 'id' => 1, + 'parent_id' => 10, + 'invoice_id' => 1, + 'total_amount' => 100, + 'paid_amount' => 30, + 'balance' => 70, + 'number_of_installments' => 1, + 'payment_method' => 'Cash', + 'payment_date' => '2025-01-05', + 'school_year' => '2025-2026', + 'status' => 'Paid', + ], + [ + 'id' => 2, + 'parent_id' => 10, + 'invoice_id' => 1, + 'total_amount' => 100, + 'paid_amount' => 20, + 'balance' => 50, + 'number_of_installments' => 1, + 'payment_method' => 'Credit Card', + 'payment_date' => '2025-01-06', + 'school_year' => '2025-2026', + 'status' => 'Paid', + ], + [ + 'id' => 3, + 'parent_id' => 10, + 'invoice_id' => 1, + 'total_amount' => 100, + 'paid_amount' => 50, + 'balance' => 0, + 'number_of_installments' => 1, + 'payment_method' => 'Cash', + 'payment_date' => '2025-01-07', + 'school_year' => '2025-2026', + 'status' => 'void', + ], + [ + 'id' => 4, + 'parent_id' => 11, + 'invoice_id' => 2, + 'total_amount' => 40, + 'paid_amount' => 10, + 'balance' => 30, + 'number_of_installments' => 1, + 'payment_method' => 'Check', + 'payment_date' => '2025-01-08', + 'school_year' => '2025-2026', + 'status' => 'Paid', + ], + ]); + + $service = new FinancialPaymentService(); + + $payments = $service->paymentsByInvoice('2025-2026', '2025-01-01', '2025-01-31'); + $breakdown = $service->paymentBreakdown('2025-2026', '2025-01-01', '2025-01-31'); + $totals = $service->paymentTotals('2025-2026', '2025-01-01', '2025-01-31'); + + $map = []; + foreach ($payments as $row) { + $map[(int) $row['invoice_id']] = (float) $row['paid_amount']; + } + + $this->assertSame(50.0, $map[1]); + $this->assertSame(10.0, $map[2]); + + $this->assertSame(30.0, (float) ($breakdown[1]['cash'] ?? 0)); + $this->assertSame(20.0, (float) ($breakdown[1]['credit'] ?? 0)); + $this->assertSame(10.0, (float) ($breakdown[2]['check'] ?? 0)); + + $this->assertSame(60.0, $totals['total_all']); + $this->assertSame(30.0, $totals['total_cash']); + $this->assertSame(20.0, $totals['total_credit']); + $this->assertSame(10.0, $totals['total_check']); + } +} diff --git a/tests/Unit/Services/Finance/FinancialPdfReportServiceTest.php b/tests/Unit/Services/Finance/FinancialPdfReportServiceTest.php new file mode 100644 index 00000000..021c05ad --- /dev/null +++ b/tests/Unit/Services/Finance/FinancialPdfReportServiceTest.php @@ -0,0 +1,37 @@ + '2025-2026', + 'totalCharges' => 100, + 'totalExtraCharges' => 20, + 'totalDiscounts' => 10, + 'totalRefunds' => 5, + 'totalExpenses' => 15, + 'totalReimbursements' => 8, + 'donationToSchool' => 3, + 'netAmount' => 85, + 'amountCollected' => 60, + 'totalUnpaid' => 40, + ]; + + $pdf = $service->buildPdf($summary); + + $this->assertIsString($pdf); + $this->assertNotSame('', $pdf); + } +} diff --git a/tests/Unit/Services/Finance/FinancialReportServiceTest.php b/tests/Unit/Services/Finance/FinancialReportServiceTest.php new file mode 100644 index 00000000..4b92f0d4 --- /dev/null +++ b/tests/Unit/Services/Finance/FinancialReportServiceTest.php @@ -0,0 +1,126 @@ +insert([ + 'id' => 10, + 'school_id' => 1, + 'firstname' => 'Parent', + 'lastname' => 'User', + 'cellphone' => '5555555555', + 'email' => 'parent@example.com', + 'address_street' => '123 Main', + 'city' => 'City', + 'state' => 'ST', + 'zip' => '12345', + 'accept_school_policy' => 1, + 'is_verified' => 1, + 'status' => 'Active', + 'is_suspended' => 0, + 'failed_attempts' => 0, + 'password' => bcrypt('secret'), + 'semester' => 'Fall', + 'school_year' => '2025-2026', + ]); + + DB::table('invoices')->insert([ + 'id' => 1, + 'parent_id' => 10, + 'invoice_number' => 'INV-001', + 'total_amount' => 100, + 'balance' => 40, + 'paid_amount' => 60, + 'has_discount' => 1, + 'issue_date' => '2025-01-01', + 'due_date' => '2025-02-01', + 'status' => 'unpaid', + 'school_year' => '2025-2026', + ]); + + DB::table('payments')->insert([ + 'id' => 1, + 'parent_id' => 10, + 'invoice_id' => 1, + 'total_amount' => 100, + 'paid_amount' => 60, + 'balance' => 40, + 'number_of_installments' => 1, + 'payment_method' => 'Cash', + 'payment_date' => '2025-01-02', + 'school_year' => '2025-2026', + 'status' => 'Paid', + ]); + + DB::table('refunds')->insert([ + 'id' => 1, + 'parent_id' => 10, + 'school_year' => '2025-2026', + 'invoice_id' => 1, + 'refund_amount' => 5, + 'status' => 'Paid', + 'refund_paid_amount' => 5, + ]); + + DB::table('discount_usages')->insert([ + 'id' => 1, + 'voucher_id' => 1, + 'invoice_id' => 1, + 'parent_id' => 10, + 'discount_amount' => 10, + 'school_year' => '2025-2026', + ]); + + DB::table('expenses')->insert([ + 'id' => 1, + 'category' => 'Expense', + 'amount' => 20, + 'date_of_purchase' => '2025-01-05', + 'purchased_by' => 10, + 'added_by' => 1, + 'school_year' => '2025-2026', + 'semester' => 'Fall', + 'status' => 'approved', + ]); + + DB::table('reimbursements')->insert([ + 'id' => 1, + 'expense_id' => 1, + 'amount' => 15, + 'reimbursed_to' => 10, + 'added_by' => 1, + 'status' => 'Paid', + 'school_year' => '2025-2026', + 'semester' => 'Fall', + ]); + + $service = new FinancialReportService( + new FinancialPaymentService(), + new FinancialSchoolYearService() + ); + + $report = $service->getReport('2025-01-01', '2025-12-31', '2025-2026'); + + $this->assertSame('2025-2026', $report['selectedYear']); + $this->assertSame(['2025-2026'], $report['schoolYears']); + $this->assertCount(1, $report['invoices']); + $this->assertCount(1, $report['payments']); + $this->assertCount(1, $report['refunds']); + $this->assertCount(1, $report['discounts']); + $this->assertCount(1, $report['expenses']); + $this->assertCount(1, $report['reimbursements']); + $this->assertSame(60.0, $report['paymentTotals']['total_all']); + } +} diff --git a/tests/Unit/Services/Finance/FinancialSchoolYearServiceTest.php b/tests/Unit/Services/Finance/FinancialSchoolYearServiceTest.php new file mode 100644 index 00000000..af82841c --- /dev/null +++ b/tests/Unit/Services/Finance/FinancialSchoolYearServiceTest.php @@ -0,0 +1,50 @@ +insert([ + [ + 'id' => 1, + 'parent_id' => 1, + 'invoice_number' => 'INV-100', + 'total_amount' => 10, + 'balance' => 10, + 'paid_amount' => 0, + 'has_discount' => 0, + 'issue_date' => '2024-01-01', + 'due_date' => '2024-02-01', + 'status' => 'unpaid', + 'school_year' => '2024-2025', + ], + [ + 'id' => 2, + 'parent_id' => 1, + 'invoice_number' => 'INV-101', + 'total_amount' => 10, + 'balance' => 10, + 'paid_amount' => 0, + 'has_discount' => 0, + 'issue_date' => '2025-01-01', + 'due_date' => '2025-02-01', + 'status' => 'unpaid', + 'school_year' => '2025-2026', + ], + ]); + + $service = new FinancialSchoolYearService(); + $years = $service->listYears(); + + $this->assertSame(['2025-2026', '2024-2025'], $years); + } +} diff --git a/tests/Unit/Services/Finance/FinancialSummaryServiceTest.php b/tests/Unit/Services/Finance/FinancialSummaryServiceTest.php new file mode 100644 index 00000000..ef01e209 --- /dev/null +++ b/tests/Unit/Services/Finance/FinancialSummaryServiceTest.php @@ -0,0 +1,165 @@ +insert([ + 'id' => 2, + 'school_id' => 1, + 'firstname' => 'Parent', + 'lastname' => 'User', + 'cellphone' => '5555555555', + 'email' => 'parent@example.com', + 'address_street' => '123 Main', + 'city' => 'City', + 'state' => 'ST', + 'zip' => '12345', + 'accept_school_policy' => 1, + 'is_verified' => 1, + 'status' => 'Active', + 'is_suspended' => 0, + 'failed_attempts' => 0, + 'password' => bcrypt('secret'), + 'semester' => 'Fall', + 'school_year' => '2025-2026', + ]); + + DB::table('invoices')->insert([ + 'id' => 1, + 'parent_id' => 2, + 'invoice_number' => 'INV-001', + 'total_amount' => 100, + 'balance' => 40, + 'paid_amount' => 60, + 'has_discount' => 1, + 'issue_date' => '2025-01-10', + 'due_date' => '2025-02-10', + 'status' => 'unpaid', + 'school_year' => '2025-2026', + ]); + + DB::table('payments')->insert([ + 'id' => 1, + 'parent_id' => 2, + 'invoice_id' => 1, + 'total_amount' => 100, + 'paid_amount' => 60, + 'balance' => 40, + 'number_of_installments' => 1, + 'payment_method' => 'Cash', + 'payment_date' => '2025-01-15', + 'school_year' => '2025-2026', + 'status' => 'Paid', + ]); + + DB::table('discount_usages')->insert([ + 'id' => 1, + 'voucher_id' => 1, + 'invoice_id' => 1, + 'parent_id' => 2, + 'discount_amount' => 10, + 'school_year' => '2025-2026', + ]); + + DB::table('refunds')->insert([ + 'id' => 1, + 'parent_id' => 2, + 'school_year' => '2025-2026', + 'invoice_id' => 1, + 'refund_amount' => 5, + 'status' => 'Paid', + 'refund_paid_amount' => 5, + ]); + + DB::table('additional_charges')->insert([ + [ + 'id' => 1, + 'parent_id' => 2, + 'amount' => 20, + 'school_year' => '2025-2026', + 'status' => 'pending', + ], + [ + 'id' => 2, + 'parent_id' => 2, + 'amount' => 30, + 'invoice_id' => 1, + 'school_year' => '2025-2026', + 'status' => 'applied', + ], + ]); + + DB::table('expenses')->insert([ + [ + 'id' => 1, + 'category' => 'Donation', + 'amount' => 12, + 'date_of_purchase' => '2025-01-05', + 'purchased_by' => 2, + 'added_by' => 1, + 'school_year' => '2025-2026', + 'semester' => 'Fall', + 'status' => 'approved', + ], + [ + 'id' => 2, + 'category' => 'Expense', + 'amount' => 8, + 'date_of_purchase' => '2025-01-06', + 'purchased_by' => 2, + 'added_by' => 1, + 'school_year' => '2025-2026', + 'semester' => 'Fall', + 'status' => 'approved', + ], + ]); + + DB::table('reimbursements')->insert([ + [ + 'id' => 1, + 'expense_id' => 2, + 'amount' => 15, + 'reimbursed_to' => 2, + 'added_by' => 1, + 'status' => 'Paid', + 'school_year' => '2025-2026', + 'semester' => 'Fall', + ], + [ + 'id' => 2, + 'expense_id' => 1, + 'amount' => 7, + 'reimbursed_to' => 990002, + 'added_by' => 1, + 'status' => 'Paid', + 'school_year' => '2025-2026', + 'semester' => 'Fall', + ], + ]); + + $service = new FinancialSummaryService(new FinancialDonationRecipientService()); + $summary = $service->getSummary('2025-01-01', '2025-12-31', '2025-2026'); + + $this->assertSame(120.0, $summary['totalCharges']); + $this->assertSame(50.0, $summary['totalExtraCharges']); + $this->assertSame(10.0, $summary['totalDiscounts']); + $this->assertSame(5.0, $summary['totalRefunds']); + $this->assertSame(20.0, $summary['totalExpenses']); + $this->assertSame(15.0, $summary['totalReimbursements']); + $this->assertSame(19.0, $summary['donationToSchool']); + $this->assertSame(60.0, $summary['totalPaid']); + $this->assertSame(45.0, $summary['totalUnpaid']); + $this->assertSame(105.0, $summary['netAmount']); + } +} diff --git a/tests/Unit/Services/Finance/FinancialUnpaidParentsServiceTest.php b/tests/Unit/Services/Finance/FinancialUnpaidParentsServiceTest.php new file mode 100644 index 00000000..42ae9a36 --- /dev/null +++ b/tests/Unit/Services/Finance/FinancialUnpaidParentsServiceTest.php @@ -0,0 +1,110 @@ +insert([ + ['id' => 1, 'config_key' => 'installment_date', 'config_value' => date('Y-m-t')], + ]); + + DB::table('users')->insert([ + 'id' => 2, + 'school_id' => 1, + 'firstname' => 'Parent', + 'lastname' => 'User', + 'cellphone' => '5555555555', + 'email' => 'parent@example.com', + 'address_street' => '123 Main', + 'city' => 'City', + 'state' => 'ST', + 'zip' => '12345', + 'accept_school_policy' => 1, + 'is_verified' => 1, + 'status' => 'Active', + 'is_suspended' => 0, + 'failed_attempts' => 0, + 'password' => bcrypt('secret'), + 'semester' => 'Fall', + 'school_year' => '2025-2026', + ]); + + DB::table('invoices')->insert([ + [ + 'id' => 1, + 'parent_id' => 2, + 'invoice_number' => 'INV-001', + 'total_amount' => 100, + 'balance' => 40, + 'paid_amount' => 60, + 'has_discount' => 1, + 'issue_date' => '2025-01-10', + 'due_date' => '2025-02-10', + 'status' => 'unpaid', + 'school_year' => '2025-2026', + ], + ]); + + DB::table('payments')->insert([ + 'id' => 1, + 'parent_id' => 2, + 'invoice_id' => 1, + 'total_amount' => 100, + 'paid_amount' => 60, + 'balance' => 40, + 'number_of_installments' => 1, + 'payment_method' => 'Cash', + 'payment_date' => '2025-01-15', + 'school_year' => '2025-2026', + 'status' => 'Paid', + ]); + + DB::table('discount_usages')->insert([ + 'id' => 1, + 'voucher_id' => 1, + 'invoice_id' => 1, + 'parent_id' => 2, + 'discount_amount' => 10, + 'school_year' => '2025-2026', + ]); + + DB::table('refunds')->insert([ + 'id' => 1, + 'parent_id' => 2, + 'school_year' => '2025-2026', + 'invoice_id' => 1, + 'refund_amount' => 5, + 'status' => 'Paid', + 'refund_paid_amount' => 5, + ]); + + DB::table('additional_charges')->insert([ + 'id' => 1, + 'parent_id' => 2, + 'amount' => 20, + 'school_year' => '2025-2026', + 'status' => 'pending', + ]); + + $service = new FinancialUnpaidParentsService(new FinancialSchoolYearService()); + $result = $service->getUnpaidParents('2025-2026'); + + $this->assertSame('2025-2026', $result['schoolYear']); + $this->assertCount(1, $result['results']); + + $row = $result['results'][0]; + $this->assertSame(45.0, $row['total_balance']); + $this->assertSame('installment', $row['type']); + $this->assertSame(1, $row['has_installment']); + } +} diff --git a/tests/Unit/Services/Grading/GradingBelowSixtyServiceTest.php b/tests/Unit/Services/Grading/GradingBelowSixtyServiceTest.php new file mode 100644 index 00000000..1981b4f8 --- /dev/null +++ b/tests/Unit/Services/Grading/GradingBelowSixtyServiceTest.php @@ -0,0 +1,55 @@ +insert([ + 'id' => 100, + 'parent_id' => 10, + 'firstname' => 'Kid', + 'lastname' => 'User', + 'school_id' => 1, + 'age' => 10, + 'gender' => 'M', + 'photo_consent' => 1, + 'year_of_registration' => '2025', + 'is_active' => 1, + ]); + + DB::table('classSection')->insert([ + 'id' => 1, + 'class_section_id' => 1, + 'class_section_name' => '1A', + 'class_id' => 1, + 'semester' => 'Fall', + 'school_year' => '2025-2026', + ]); + + DB::table('semester_scores')->insert([ + 'id' => 1, + 'student_id' => 100, + 'school_id' => 1, + 'class_section_id' => 1, + 'semester' => 'Fall', + 'school_year' => '2025-2026', + 'semester_score' => 55, + ]); + + $service = new GradingBelowSixtyService(); + $rows = $service->listRows('2025-2026', 'Fall'); + + $this->assertCount(1, $rows); + $this->assertSame(100, (int) $rows[0]['student_id']); + $this->assertSame('Open', $rows[0]['status']); + } +} diff --git a/tests/Unit/Services/Grading/GradingLockServiceTest.php b/tests/Unit/Services/Grading/GradingLockServiceTest.php new file mode 100644 index 00000000..2cf59bb0 --- /dev/null +++ b/tests/Unit/Services/Grading/GradingLockServiceTest.php @@ -0,0 +1,69 @@ +insert([ + 'id' => 1, + 'class_section_id' => 1, + 'class_section_name' => '1A', + 'class_id' => 1, + 'semester' => 'Fall', + 'school_year' => '2025-2026', + ]); + + $service = new GradingLockService(); + $locked = $service->toggle(1, 'Fall', '2025-2026', 99); + + $this->assertTrue($locked); + $this->assertDatabaseHas('grading_locks', [ + 'class_section_id' => 1, + 'semester' => 'Fall', + 'school_year' => '2025-2026', + 'is_locked' => 1, + ]); + } + + public function test_lock_all_locks_sections(): void + { + DB::table('classSection')->insert([ + [ + 'id' => 1, + 'class_section_id' => 1, + 'class_section_name' => '1A', + 'class_id' => 1, + 'semester' => 'Fall', + 'school_year' => '2025-2026', + ], + [ + 'id' => 2, + 'class_section_id' => 2, + 'class_section_name' => '1B', + 'class_id' => 1, + 'semester' => 'Fall', + 'school_year' => '2025-2026', + ], + ]); + + $service = new GradingLockService(); + $count = $service->lockAll('Fall', '2025-2026', 99); + + $this->assertSame(2, $count); + $this->assertDatabaseHas('grading_locks', [ + 'class_section_id' => 2, + 'semester' => 'Fall', + 'school_year' => '2025-2026', + 'is_locked' => 1, + ]); + } +} diff --git a/tests/Unit/Services/Grading/HomeworkTrackingServiceTest.php b/tests/Unit/Services/Grading/HomeworkTrackingServiceTest.php new file mode 100644 index 00000000..88ffb099 --- /dev/null +++ b/tests/Unit/Services/Grading/HomeworkTrackingServiceTest.php @@ -0,0 +1,81 @@ +insert([ + ['id' => 1, 'config_key' => 'school_year', 'config_value' => '2025-2026'], + ['id' => 2, 'config_key' => 'semester', 'config_value' => 'Fall'], + ]); + + DB::table('classSection')->insert([ + 'id' => 1, + 'class_section_id' => 1, + 'class_section_name' => '1A', + 'class_id' => 1, + 'semester' => 'Fall', + 'school_year' => '2025-2026', + ]); + + DB::table('users')->insert([ + 'id' => 1, + 'school_id' => 1, + 'firstname' => 'Teacher', + 'lastname' => 'User', + 'cellphone' => '5555555555', + 'email' => 'teacher@example.com', + 'address_street' => '123 Main', + 'city' => 'City', + 'state' => 'ST', + 'zip' => '12345', + 'accept_school_policy' => 1, + 'is_verified' => 1, + 'status' => 'Active', + 'is_suspended' => 0, + 'failed_attempts' => 0, + 'password' => bcrypt('secret'), + 'semester' => 'Fall', + 'school_year' => '2025-2026', + ]); + + DB::table('teacher_class')->insert([ + 'id' => 1, + 'class_section_id' => 1, + 'teacher_id' => 1, + 'position' => 'main', + 'semester' => 'Fall', + 'school_year' => '2025-2026', + ]); + + DB::table('homework')->insert([ + 'id' => 1, + 'student_id' => 100, + 'school_id' => 1, + 'class_section_id' => 1, + 'updated_by' => 1, + 'homework_index' => 1, + 'score' => 90, + 'semester' => 'Fall', + 'school_year' => '2025-2026', + 'created_at' => now(), + 'updated_at' => now(), + ]); + + $service = new HomeworkTrackingService(new HomeworkTrackingCalendarService()); + $result = $service->report('Fall', '2025-2026', 1); + + $this->assertNotEmpty($result['teachers']); + $this->assertSame(1, $result['teachers'][0]['class_section_id']); + } +} diff --git a/tests/Unit/Services/Incidents/IncidentAnalysisServiceTest.php b/tests/Unit/Services/Incidents/IncidentAnalysisServiceTest.php new file mode 100644 index 00000000..36f98db5 --- /dev/null +++ b/tests/Unit/Services/Incidents/IncidentAnalysisServiceTest.php @@ -0,0 +1,62 @@ +insert([ + 'id' => 1, + 'class_section_id' => 1, + 'class_section_name' => '1A', + 'class_id' => 1, + 'semester' => 'Fall', + 'school_year' => '2025-2026', + ]); + + DB::table('incident')->insert([ + [ + 'id' => 1, + 'student_id' => 10, + 'student_name' => 'Kid Tester', + 'grade' => '1', + 'incident' => 'behavior', + 'incident_datetime' => '2025-01-02 10:00:00', + 'incident_state' => 'Closed', + 'semester' => 'Fall', + 'school_year' => '2025-2026', + ], + [ + 'id' => 2, + 'student_id' => 10, + 'student_name' => 'Kid Tester', + 'grade' => '1', + 'incident' => 'behavior', + 'incident_datetime' => '2025-01-01 10:00:00', + 'incident_state' => 'Canceled', + 'semester' => 'Fall', + 'school_year' => '2025-2026', + ], + ]); + + $service = new IncidentAnalysisService(new IncidentLookupService()); + $result = $service->analyze('2025-2026', 'Fall'); + + $this->assertCount(1, $result); + $this->assertSame('Kid Tester', $result[0]['student_name']); + $this->assertSame('1A', $result[0]['grade']); + $this->assertSame(2, $result[0]['total']); + $this->assertSame(1, $result[0]['closed']); + $this->assertSame(1, $result[0]['canceled']); + $this->assertSame('2025-01-02 10:00:00', $result[0]['last_incident']); + } +} diff --git a/tests/Unit/Services/Incidents/IncidentHistoryServiceTest.php b/tests/Unit/Services/Incidents/IncidentHistoryServiceTest.php new file mode 100644 index 00000000..f4a0e8dd --- /dev/null +++ b/tests/Unit/Services/Incidents/IncidentHistoryServiceTest.php @@ -0,0 +1,66 @@ +insert([ + 'id' => 1, + 'class_id' => 1, + 'class_section_id' => 5, + 'class_section_name' => '5A', + 'semester' => 'Fall', + 'school_year' => '2025-2026', + ]); + + DB::table('users')->insert([ + 'id' => 3, + 'school_id' => 1, + 'firstname' => 'Admin', + 'lastname' => 'User', + 'cellphone' => '5555555555', + 'email' => 'admin@example.com', + 'address_street' => '123 Main', + 'city' => 'City', + 'state' => 'ST', + 'zip' => '12345', + 'accept_school_policy' => 1, + 'is_verified' => 1, + 'status' => 'Active', + 'is_suspended' => 0, + 'failed_attempts' => 0, + 'password' => bcrypt('secret'), + 'semester' => 'Fall', + 'school_year' => '2025-2026', + ]); + + DB::table('incident')->insert([ + 'student_id' => 1, + 'student_name' => 'Student One', + 'grade' => 5, + 'incident' => 'Test incident', + 'incident_datetime' => '2025-01-01 10:00:00', + 'incident_state' => 'open', + 'updated_by_open' => 3, + 'open_description' => 'Opened', + 'semester' => 'Fall', + 'school_year' => '2025-2026', + ]); + + $service = new IncidentHistoryService(new IncidentLookupService()); + $rows = $service->processed('2025-2026', 'Fall'); + + $this->assertSame('5A', $rows[0]['grade']); + $this->assertSame('Admin User', $rows[0]['updated_by_open_name']); + } +} diff --git a/tests/Unit/Services/Incidents/IncidentLookupServiceTest.php b/tests/Unit/Services/Incidents/IncidentLookupServiceTest.php new file mode 100644 index 00000000..20332c33 --- /dev/null +++ b/tests/Unit/Services/Incidents/IncidentLookupServiceTest.php @@ -0,0 +1,79 @@ +insert([ + 'id' => 1, + 'class_id' => 1, + 'class_section_id' => 10, + 'class_section_name' => '1A', + 'semester' => 'Fall', + 'school_year' => '2025-2026', + ]); + + DB::table('students')->insert([ + 'id' => 1, + 'school_id' => 'S1', + 'firstname' => 'Test', + 'lastname' => 'Student', + 'age' => 8, + 'gender' => 'Male', + 'photo_consent' => 1, + 'parent_id' => 1, + 'year_of_registration' => '2025', + 'school_year' => '2025-2026', + ]); + + DB::table('student_class')->insert([ + 'student_id' => 1, + 'class_section_id' => 10, + 'semester' => 'Fall', + 'school_year' => '2025-2026', + ]); + + $service = new IncidentLookupService(); + $grades = $service->gradeOptionsWithActiveStudents('2025-2026'); + + $this->assertSame([['id' => 10, 'name' => '1A']], $grades); + } + + public function test_updater_name_map(): void + { + DB::table('users')->insert([ + 'id' => 1, + 'school_id' => 1, + 'firstname' => 'Jane', + 'lastname' => 'Doe', + 'cellphone' => '5555555555', + 'email' => 'jane@example.com', + 'address_street' => '123 Main', + 'city' => 'City', + 'state' => 'ST', + 'zip' => '12345', + 'accept_school_policy' => 1, + 'is_verified' => 1, + 'status' => 'Active', + 'is_suspended' => 0, + 'failed_attempts' => 0, + 'password' => bcrypt('secret'), + 'semester' => 'Fall', + 'school_year' => '2025-2026', + ]); + + $service = new IncidentLookupService(); + $map = $service->updaterNameMap([1]); + + $this->assertSame('Jane Doe', $map[1]); + } +} diff --git a/tests/Unit/Services/Invoices/InvoiceConfigServiceTest.php b/tests/Unit/Services/Invoices/InvoiceConfigServiceTest.php new file mode 100644 index 00000000..4e5631f8 --- /dev/null +++ b/tests/Unit/Services/Invoices/InvoiceConfigServiceTest.php @@ -0,0 +1,38 @@ +insert([ + ['config_key' => 'school_year', 'config_value' => '2025-2026'], + ['config_key' => 'semester', 'config_value' => 'Fall'], + ['config_key' => 'due_date', 'config_value' => '2025-09-01'], + ['config_key' => 'grade_fee', 'config_value' => '9'], + ['config_key' => 'first_student_fee', 'config_value' => '350'], + ['config_key' => 'second_student_fee', 'config_value' => '200'], + ['config_key' => 'youth_fee', 'config_value' => '180'], + ['config_key' => 'refund_deadline', 'config_value' => '2025-10-01'], + ]); + + $service = new InvoiceConfigService(); + + $this->assertSame('2025-2026', $service->getSchoolYear()); + $this->assertSame('Fall', $service->getSemester()); + $this->assertSame('2025-09-01', $service->getDueDate()); + $this->assertSame(9, $service->getGradeFee()); + $this->assertSame(350.0, $service->getFirstStudentFee()); + $this->assertSame(200.0, $service->getSecondStudentFee()); + $this->assertSame(180.0, $service->getYouthFee()); + $this->assertSame('2025-10-01', $service->getRefundDeadline()); + } +} diff --git a/tests/Unit/Services/Invoices/InvoiceGenerationServiceTest.php b/tests/Unit/Services/Invoices/InvoiceGenerationServiceTest.php new file mode 100644 index 00000000..3c37a14e --- /dev/null +++ b/tests/Unit/Services/Invoices/InvoiceGenerationServiceTest.php @@ -0,0 +1,92 @@ +insert([ + 'id' => 10, + 'school_id' => 1, + 'firstname' => 'Parent', + 'lastname' => 'User', + 'cellphone' => '5555555555', + 'email' => 'parent@example.com', + 'address_street' => '123 Main', + 'city' => 'City', + 'state' => 'ST', + 'zip' => '12345', + 'accept_school_policy' => 1, + 'is_verified' => 1, + 'status' => 'Active', + 'is_suspended' => 0, + 'failed_attempts' => 0, + 'password' => bcrypt('secret'), + 'semester' => 'Fall', + 'school_year' => '2025-2026', + ]); + + DB::table('students')->insert([ + 'id' => 100, + 'parent_id' => 10, + 'firstname' => 'Kid', + 'lastname' => 'User', + 'school_id' => 1, + 'is_active' => 1, + ]); + + DB::table('classSection')->insert([ + 'class_section_id' => 1, + 'class_section_name' => '1A', + 'class_id' => 1, + ]); + + DB::table('student_class')->insert([ + 'student_id' => 100, + 'class_section_id' => 1, + 'school_year' => '2025-2026', + 'is_event_only' => 0, + ]); + + DB::table('enrollments')->insert([ + 'student_id' => 100, + 'parent_id' => 10, + 'class_section_id' => 1, + 'enrollment_status' => 'enrolled', + 'school_year' => '2025-2026', + ]); + + $config = new InvoiceConfigService(); + $grades = new InvoiceGradeService($config->getGradeFee()); + $tuition = new InvoiceTuitionService( + $grades, + $config->getGradeFee(), + $config->getFirstStudentFee(), + $config->getSecondStudentFee(), + $config->getYouthFee(), + $config->getTimezone() + ); + $service = new InvoiceGenerationService($config, $tuition); + + $result = $service->generateInvoice(10, '2025-2026'); + + $this->assertTrue($result['ok']); + $this->assertNotNull($result['insert_id']); + $this->assertDatabaseHas('invoices', [ + 'id' => $result['insert_id'], + 'parent_id' => 10, + 'school_year' => '2025-2026', + ]); + } +} diff --git a/tests/Unit/Services/Invoices/InvoiceGradeServiceTest.php b/tests/Unit/Services/Invoices/InvoiceGradeServiceTest.php new file mode 100644 index 00000000..039b8225 --- /dev/null +++ b/tests/Unit/Services/Invoices/InvoiceGradeServiceTest.php @@ -0,0 +1,37 @@ +assertTrue($service->isKindergarten('KG')); + $this->assertSame(1, $service->gradeLevelInt('K')); + $this->assertSame(-1, $service->gradeLevelInt('PK')); + $this->assertSame(10, $service->gradeLevelInt('Youth')); + $this->assertSame(5, $service->gradeLevelInt('Grade 5')); + + $info = $service->getGradeLevel('5A'); + $this->assertSame(5, $info['level']); + $this->assertSame('A', $info['suffix']); + } + + public function test_compare_grades_orders_numeric_then_suffix(): void + { + $service = new InvoiceGradeService(9); + + $this->assertSame(-1, $service->compareGrades('1', '2')); + $this->assertSame(1, $service->compareGrades('2', '1')); + $this->assertSame(-1, $service->compareGrades('2A', '2B')); + $this->assertSame(0, $service->compareGrades('K', 'K')); + } +} diff --git a/tests/Unit/Services/Invoices/InvoiceManagementServiceTest.php b/tests/Unit/Services/Invoices/InvoiceManagementServiceTest.php new file mode 100644 index 00000000..7c3ff7bf --- /dev/null +++ b/tests/Unit/Services/Invoices/InvoiceManagementServiceTest.php @@ -0,0 +1,107 @@ +insert([ + ['id' => 1, 'name' => 'parent', 'slug' => 'parent', 'is_active' => 1], + ]); + + DB::table('users')->insert([ + 'id' => 10, + 'school_id' => 1, + 'firstname' => 'Parent', + 'lastname' => 'User', + 'cellphone' => '5555555555', + 'email' => 'parent@example.com', + 'address_street' => '123 Main', + 'city' => 'City', + 'state' => 'ST', + 'zip' => '12345', + 'accept_school_policy' => 1, + 'is_verified' => 1, + 'status' => 'Active', + 'is_suspended' => 0, + 'failed_attempts' => 0, + 'password' => bcrypt('secret'), + 'semester' => 'Fall', + 'school_year' => '2025-2026', + ]); + + DB::table('user_roles')->insert([ + 'user_id' => 10, + 'role_id' => 1, + ]); + + DB::table('students')->insert([ + 'id' => 100, + 'parent_id' => 10, + 'firstname' => 'Kid', + 'lastname' => 'User', + 'school_id' => 1, + 'is_active' => 1, + ]); + + DB::table('classSection')->insert([ + 'class_section_id' => 1, + 'class_section_name' => '1A', + 'class_id' => 1, + ]); + + DB::table('student_class')->insert([ + 'student_id' => 100, + 'class_section_id' => 1, + 'school_year' => '2025-2026', + 'is_event_only' => 0, + ]); + + DB::table('enrollments')->insert([ + 'student_id' => 100, + 'parent_id' => 10, + 'class_section_id' => 1, + 'enrollment_status' => 'enrolled', + 'school_year' => '2025-2026', + ]); + + DB::table('invoices')->insert([ + 'id' => 1, + 'parent_id' => 10, + 'invoice_number' => 'INV-001', + 'total_amount' => 100, + 'balance' => 100, + 'paid_amount' => 0, + 'has_discount' => 0, + 'issue_date' => '2025-01-10', + 'status' => 'unpaid', + 'school_year' => '2025-2026', + ]); + + DB::table('refunds')->insert([ + 'id' => 1, + 'parent_id' => 10, + 'school_year' => '2025-2026', + 'invoice_id' => 1, + 'refund_amount' => 5, + 'status' => 'Paid', + 'refund_paid_amount' => 5, + ]); + + $service = new InvoiceManagementService(new InvoiceConfigService()); + $data = $service->getManagementData('2025-2026'); + + $this->assertSame('2025-2026', $data['schoolYear']); + $this->assertCount(1, $data['invoices']); + $this->assertSame(5.0, $data['invoices'][0]['refund_amount']); + } +} diff --git a/tests/Unit/Services/Invoices/InvoicePaymentServiceTest.php b/tests/Unit/Services/Invoices/InvoicePaymentServiceTest.php new file mode 100644 index 00000000..801c7160 --- /dev/null +++ b/tests/Unit/Services/Invoices/InvoicePaymentServiceTest.php @@ -0,0 +1,63 @@ +insert([ + [ + 'id' => 1, + 'parent_id' => 10, + 'invoice_number' => 'INV-001', + 'total_amount' => 100, + 'balance' => 40, + 'paid_amount' => 60, + 'has_discount' => 0, + 'issue_date' => '2025-01-10', + 'status' => 'unpaid', + 'school_year' => '2025-2026', + ], + ]); + + DB::table('payments')->insert([ + 'id' => 1, + 'parent_id' => 10, + 'invoice_id' => 1, + 'total_amount' => 100, + 'paid_amount' => 60, + 'balance' => 40, + 'number_of_installments' => 1, + 'payment_method' => 'Cash', + 'payment_date' => '2025-01-15', + 'school_year' => '2025-2026', + 'status' => 'Paid', + ]); + + DB::table('refunds')->insert([ + 'id' => 1, + 'parent_id' => 10, + 'school_year' => '2025-2026', + 'invoice_id' => 1, + 'refund_amount' => 5, + 'status' => 'Paid', + 'refund_paid_amount' => 5, + ]); + + $service = new InvoicePaymentService(new InvoiceConfigService()); + $data = $service->getParentInvoiceSummary(10, '2025-2026'); + + $this->assertSame('2025-2026', $data['selectedYear']); + $this->assertSame(5.0, $data['invoices'][0]['refund_amount']); + $this->assertSame(60.0, $data['invoices'][0]['last_paid_amount']); + } +} diff --git a/tests/Unit/Services/Invoices/InvoicePdfServiceTest.php b/tests/Unit/Services/Invoices/InvoicePdfServiceTest.php new file mode 100644 index 00000000..637c1f24 --- /dev/null +++ b/tests/Unit/Services/Invoices/InvoicePdfServiceTest.php @@ -0,0 +1,91 @@ +insert([ + 'id' => 10, + 'school_id' => 1, + 'firstname' => 'Parent', + 'lastname' => 'User', + 'cellphone' => '5555555555', + 'email' => 'parent@example.com', + 'address_street' => '123 Main', + 'city' => 'City', + 'state' => 'ST', + 'zip' => '12345', + 'accept_school_policy' => 1, + 'is_verified' => 1, + 'status' => 'Active', + 'is_suspended' => 0, + 'failed_attempts' => 0, + 'password' => bcrypt('secret'), + 'semester' => 'Fall', + 'school_year' => '2025-2026', + ]); + + DB::table('students')->insert([ + 'id' => 100, + 'parent_id' => 10, + 'firstname' => 'Kid', + 'lastname' => 'User', + 'school_id' => 1, + 'is_active' => 1, + ]); + + DB::table('classSection')->insert([ + 'class_section_id' => 1, + 'class_section_name' => '1A', + 'class_id' => 1, + ]); + + DB::table('student_class')->insert([ + 'student_id' => 100, + 'class_section_id' => 1, + 'school_year' => '2025-2026', + 'is_event_only' => 0, + ]); + + DB::table('enrollments')->insert([ + 'student_id' => 100, + 'parent_id' => 10, + 'class_section_id' => 1, + 'enrollment_status' => 'enrolled', + 'school_year' => '2025-2026', + ]); + + DB::table('invoices')->insert([ + 'id' => 1, + 'parent_id' => 10, + 'invoice_number' => 'INV-001', + 'total_amount' => 100, + 'balance' => 100, + 'paid_amount' => 0, + 'has_discount' => 0, + 'issue_date' => '2025-01-10', + 'status' => 'unpaid', + 'school_year' => '2025-2026', + ]); + + $config = new InvoiceConfigService(); + $grades = new InvoiceGradeService($config->getGradeFee()); + $service = new InvoicePdfService($config, $grades); + + $pdf = $service->buildPdf(1); + + $this->assertIsString($pdf); + $this->assertNotSame('', $pdf); + } +} diff --git a/tests/Unit/Services/Invoices/InvoiceTuitionServiceTest.php b/tests/Unit/Services/Invoices/InvoiceTuitionServiceTest.php new file mode 100644 index 00000000..ebedc709 --- /dev/null +++ b/tests/Unit/Services/Invoices/InvoiceTuitionServiceTest.php @@ -0,0 +1,60 @@ +insert([ + ['class_section_id' => 1, 'class_section_name' => '1', 'class_id' => 1], + ['class_section_id' => 2, 'class_section_name' => '2', 'class_id' => 2], + ['class_section_id' => 3, 'class_section_name' => '10', 'class_id' => 10], + ]); + + $grades = new InvoiceGradeService(9); + $service = new InvoiceTuitionService($grades, 9, 350.0, 200.0, 180.0, 'UTC'); + + $registeredKids = [ + ['class_section_id' => 1], + ['class_section_id' => 2], + ['class_section_id' => 3], + ]; + + $total = $service->calculateTuitionFee($registeredKids, [], date('Y-m-d', strtotime('+10 days'))); + + $this->assertSame(730.0, $total); + } + + public function test_refund_deadline_includes_withdrawn_when_passed(): void + { + DB::table('classSection')->insert([ + ['class_section_id' => 1, 'class_section_name' => '1', 'class_id' => 1], + ['class_section_id' => 2, 'class_section_name' => '2', 'class_id' => 2], + ]); + + $grades = new InvoiceGradeService(9); + $service = new InvoiceTuitionService($grades, 9, 350.0, 200.0, 180.0, 'UTC'); + + $registeredKids = [ + ['class_section_id' => 1], + ]; + $withdrawnKids = [ + ['class_section_id' => 2], + ]; + + $totalAllowed = $service->calculateTuitionFee($registeredKids, $withdrawnKids, date('Y-m-d', strtotime('+10 days'))); + $totalBlocked = $service->calculateTuitionFee($registeredKids, $withdrawnKids, date('Y-m-d', strtotime('-10 days'))); + + $this->assertSame(350.0, $totalAllowed); + $this->assertSame(550.0, $totalBlocked); + } +} diff --git a/tests/Unit/Services/Payments/PaymentBalanceServiceTest.php b/tests/Unit/Services/Payments/PaymentBalanceServiceTest.php new file mode 100644 index 00000000..5fa4e6d7 --- /dev/null +++ b/tests/Unit/Services/Payments/PaymentBalanceServiceTest.php @@ -0,0 +1,40 @@ +insert([ + 'id' => 1, + 'parent_id' => 10, + 'invoice_id' => 1, + 'total_amount' => 100, + 'paid_amount' => 0, + 'balance' => 100, + 'number_of_installments' => 1, + 'payment_method' => 'cash', + 'payment_date' => '2025-01-01', + 'school_year' => '2025-2026', + 'status' => 'Pending', + ]); + + $service = new PaymentBalanceService(); + $updated = $service->updateBalance(1, 40); + + $this->assertTrue($updated); + $this->assertDatabaseHas('payments', [ + 'id' => 1, + 'paid_amount' => 40, + 'balance' => 60, + ]); + } +} diff --git a/tests/Unit/Services/Payments/PaymentEnrollmentEventServiceTest.php b/tests/Unit/Services/Payments/PaymentEnrollmentEventServiceTest.php new file mode 100644 index 00000000..6917bbba --- /dev/null +++ b/tests/Unit/Services/Payments/PaymentEnrollmentEventServiceTest.php @@ -0,0 +1,53 @@ +expectException(\RuntimeException::class); + + $service->buildEventData(999, [], '2025-2026', 'Fall'); + } + + public function test_build_event_data_returns_parent_payload_when_no_students(): void + { + DB::table('users')->insert([ + 'id' => 10, + 'school_id' => 1, + 'firstname' => 'Parent', + 'lastname' => 'User', + 'cellphone' => '555-555-5555', + 'email' => 'parent@example.com', + 'address_street' => '123 Main', + 'city' => 'City', + 'state' => 'ST', + 'zip' => '12345', + 'accept_school_policy' => 1, + 'is_verified' => 1, + 'status' => 'Active', + 'is_suspended' => 0, + 'failed_attempts' => 0, + 'password' => bcrypt('secret'), + 'semester' => 'Fall', + 'school_year' => '2025-2026', + ]); + + $service = new PaymentEnrollmentEventService(); + [$parent, $students] = $service->buildEventData(10, [], '2025-2026', 'Fall', 'https://example.test/login'); + + $this->assertSame(10, $parent['user_id']); + $this->assertSame('parent@example.com', $parent['email']); + $this->assertSame([], $students); + } +} diff --git a/tests/Unit/Services/Payments/PaymentEventChargesServiceTest.php b/tests/Unit/Services/Payments/PaymentEventChargesServiceTest.php new file mode 100644 index 00000000..c11f82ca --- /dev/null +++ b/tests/Unit/Services/Payments/PaymentEventChargesServiceTest.php @@ -0,0 +1,82 @@ +insert([ + 'id' => 1, + 'school_id' => 'S1', + 'firstname' => 'Student', + 'lastname' => 'One', + 'dob' => '2010-01-01', + 'age' => 14, + 'gender' => 'Male', + 'is_active' => 1, + 'registration_grade' => '5', + 'is_new' => 1, + 'photo_consent' => 1, + 'parent_id' => 10, + 'registration_date' => '2025-01-01', + 'tuition_paid' => 0, + 'rfid_tag' => null, + 'semester' => 'Fall', + 'year_of_registration' => '2025', + 'school_year' => '2025-2026', + ]); + + DB::table('enrollments')->insert([ + 'id' => 1, + 'student_id' => 1, + 'class_section_id' => 100, + 'parent_id' => 10, + 'enrollment_date' => '2025-09-01', + 'enrollment_status' => 'enrolled', + 'withdrawal_date' => null, + 'is_withdrawn' => 0, + 'admission_status' => 'accepted', + 'semester' => 'Fall', + 'school_year' => '2025-2026', + ]); + + DB::table('classSection')->insert([ + 'id' => 1, + 'class_id' => 9, + 'class_section_id' => 100, + 'class_section_name' => 'Section A', + 'created_at' => null, + 'updated_at' => null, + 'semester' => 'Fall', + 'school_year' => '2025-2026', + ]); + + DB::table('student_class')->insert([ + 'id' => 1, + 'student_id' => 1, + 'class_section_id' => 100, + 'is_event_only' => 0, + 'semester' => 'Fall', + 'school_year' => '2025-2026', + 'description' => null, + 'created_at' => '2025-09-01 00:00:00', + 'updated_at' => '2025-09-01 00:00:00', + 'updated_by' => null, + ]); + + $service = new PaymentEventChargesService(); + $students = $service->getEnrolledStudents(10, '2025-2026'); + + $this->assertCount(1, $students); + $this->assertSame(1, $students[0]['id']); + $this->assertSame('9', (string) $students[0]['grade']); + } +} diff --git a/tests/Unit/Services/Payments/PaymentLookupServiceTest.php b/tests/Unit/Services/Payments/PaymentLookupServiceTest.php new file mode 100644 index 00000000..a47e0fd1 --- /dev/null +++ b/tests/Unit/Services/Payments/PaymentLookupServiceTest.php @@ -0,0 +1,50 @@ +insert([ + 'id' => 1, + 'parent_id' => 10, + 'invoice_id' => 1, + 'total_amount' => 100, + 'paid_amount' => 20, + 'balance' => 80, + 'number_of_installments' => 1, + 'payment_method' => 'cash', + 'payment_date' => '2025-01-01', + 'school_year' => '2025-2026', + 'status' => 'Pending', + ]); + + DB::table('payments')->insert([ + 'id' => 2, + 'parent_id' => 10, + 'invoice_id' => 2, + 'total_amount' => 100, + 'paid_amount' => 20, + 'balance' => 80, + 'number_of_installments' => 1, + 'payment_method' => 'cash', + 'payment_date' => '2024-01-01', + 'school_year' => '2024-2025', + 'status' => 'Pending', + ]); + + $service = new PaymentLookupService(); + $results = $service->getByParent(10, '2025-2026'); + + $this->assertCount(1, $results); + $this->assertSame(1, $results[0]['id']); + } +} diff --git a/tests/Unit/Services/Payments/PaymentManualServiceTest.php b/tests/Unit/Services/Payments/PaymentManualServiceTest.php new file mode 100644 index 00000000..3f80961c --- /dev/null +++ b/tests/Unit/Services/Payments/PaymentManualServiceTest.php @@ -0,0 +1,61 @@ +expectException(\InvalidArgumentException::class); + + $service->recordPayment(1, 10.0, 'wire', null, null, null, null); + } + + public function test_suggest_returns_matches(): void + { + DB::table('users')->insert([ + 'id' => 10, + 'school_id' => 1, + 'firstname' => 'Parent', + 'lastname' => 'User', + 'cellphone' => '555-555-5555', + 'email' => 'parent@example.com', + 'address_street' => '123 Main', + 'city' => 'City', + 'state' => 'ST', + 'zip' => '12345', + 'accept_school_policy' => 1, + 'is_verified' => 1, + 'status' => 'Active', + 'is_suspended' => 0, + 'failed_attempts' => 0, + 'password' => bcrypt('secret'), + 'semester' => 'Fall', + 'school_year' => '2025-2026', + ]); + + $invoiceService = Mockery::mock(DiscountInvoiceService::class); + $enrollmentService = Mockery::mock(PaymentEnrollmentEventService::class); + + $service = new PaymentManualService($invoiceService, $enrollmentService); + $results = $service->suggest('parent@example.com'); + + $this->assertCount(1, $results); + $this->assertSame('parent@example.com', $results[0]['value']); + } +} diff --git a/tests/Unit/Services/Payments/PaymentNotificationDispatchServiceTest.php b/tests/Unit/Services/Payments/PaymentNotificationDispatchServiceTest.php new file mode 100644 index 00000000..d27d7dc6 --- /dev/null +++ b/tests/Unit/Services/Payments/PaymentNotificationDispatchServiceTest.php @@ -0,0 +1,40 @@ +insert([ + ['id' => 2001, 'config_key' => 'school_year', 'config_value' => '2025-2026'], + ['id' => 2002, 'config_key' => 'semester', 'config_value' => 'Fall'], + ]); + + $service = new PaymentNotificationDispatchService(); + $service->notifyUser(10, 'Payment Reminder', 'Reminder body', ['in_app']); + + $this->assertDatabaseHas('notifications', [ + 'title' => 'Payment Reminder', + 'message' => 'Reminder body', + 'target_group' => 'user', + 'status' => 'sent', + 'school_year' => '2025-2026', + 'semester' => 'Fall', + ]); + + $this->assertDatabaseHas('user_notifications', [ + 'user_id' => 10, + 'delivered' => 1, + 'school_year' => '2025-2026', + 'semester' => 'Fall', + ]); + } +} diff --git a/tests/Unit/Services/Payments/PaymentNotificationServiceTest.php b/tests/Unit/Services/Payments/PaymentNotificationServiceTest.php new file mode 100644 index 00000000..4618093e --- /dev/null +++ b/tests/Unit/Services/Payments/PaymentNotificationServiceTest.php @@ -0,0 +1,134 @@ +insert([ + 'id' => 10, + 'school_id' => 1, + 'firstname' => 'Parent', + 'lastname' => 'User', + 'cellphone' => '555-555-5555', + 'email' => 'parent@example.com', + 'address_street' => '123 Main', + 'city' => 'City', + 'state' => 'ST', + 'zip' => '12345', + 'accept_school_policy' => 1, + 'is_verified' => 1, + 'status' => 'Active', + 'is_suspended' => 0, + 'failed_attempts' => 0, + 'password' => bcrypt('secret'), + 'semester' => 'Fall', + 'school_year' => '2025-2026', + ]); + + DB::table('invoices')->insert([ + 'id' => 1, + 'parent_id' => 10, + 'invoice_number' => 'INV-1', + 'total_amount' => 100, + 'balance' => 100, + 'paid_amount' => 0, + 'has_discount' => 0, + 'issue_date' => '2025-01-01', + 'status' => 'unpaid', + 'school_year' => '2025-2026', + 'semester' => 'Fall', + ]); + + $emailService = Mockery::mock(EmailService::class); + $emailService->shouldReceive('send') + ->once() + ->with( + 'parent@example.com', + Mockery::type('string'), + Mockery::type('string'), + 'finance' + ) + ->andReturn(true); + + $dispatchService = Mockery::mock(PaymentNotificationDispatchService::class); + $dispatchService->shouldNotReceive('notifyUser'); + + $service = new PaymentNotificationService($emailService, $dispatchService); + $result = $service->send([ + 'parent_id' => 10, + 'type' => 'installment', + 'school_year' => '2025-2026', + ]); + + $this->assertSame(1, $result['sent']); + $this->assertDatabaseHas('payment_notification_logs', [ + 'parent_id' => 10, + 'status' => 'sent', + 'type' => 'installment', + ]); + } + + public function test_list_logs_filters_by_type(): void + { + DB::table('users')->insert([ + 'id' => 11, + 'school_id' => 1, + 'firstname' => 'Parent', + 'lastname' => 'Two', + 'cellphone' => '555-555-5556', + 'email' => 'parent2@example.com', + 'address_street' => '123 Main', + 'city' => 'City', + 'state' => 'ST', + 'zip' => '12345', + 'accept_school_policy' => 1, + 'is_verified' => 1, + 'status' => 'Active', + 'is_suspended' => 0, + 'failed_attempts' => 0, + 'password' => bcrypt('secret'), + 'semester' => 'Fall', + 'school_year' => '2025-2026', + ]); + + DB::table('payment_notification_logs')->insert([ + 'id' => 1, + 'parent_id' => 11, + 'invoice_id' => null, + 'school_year' => '2025-2026', + 'period_year' => 2025, + 'period_month' => 9, + 'type' => 'installment', + 'to_email' => 'parent2@example.com', + 'cc_email' => null, + 'head_fa_notified' => 0, + 'subject' => 'Reminder', + 'body' => 'Body', + 'status' => 'sent', + 'error_message' => null, + 'balance_snapshot' => 50, + 'sent_at' => '2025-09-01 00:00:00', + ]); + + $emailService = Mockery::mock(EmailService::class); + $dispatchService = Mockery::mock(PaymentNotificationDispatchService::class); + $service = new PaymentNotificationService($emailService, $dispatchService); + + $logs = $service->listLogs(null, null, 'installment'); + + $this->assertCount(1, $logs); + $this->assertSame(11, $logs[0]->parent_id); + } +} diff --git a/tests/Unit/Services/Payments/PaymentPlanServiceTest.php b/tests/Unit/Services/Payments/PaymentPlanServiceTest.php new file mode 100644 index 00000000..119ffa30 --- /dev/null +++ b/tests/Unit/Services/Payments/PaymentPlanServiceTest.php @@ -0,0 +1,70 @@ +insert([ + ['id' => 2001, 'config_key' => 'school_year', 'config_value' => '2025-2026'], + ['id' => 2002, 'config_key' => 'semester', 'config_value' => 'Fall'], + ]); + + DB::table('users')->insert([ + 'id' => 10, + 'school_id' => 1, + 'firstname' => 'Parent', + 'lastname' => 'User', + 'cellphone' => '5555555555', + 'email' => 'parent@example.com', + 'address_street' => '123 Main', + 'city' => 'City', + 'state' => 'ST', + 'zip' => '12345', + 'accept_school_policy' => 1, + 'is_verified' => 1, + 'status' => 'Active', + 'is_suspended' => 0, + 'failed_attempts' => 0, + 'password' => bcrypt('secret'), + 'semester' => 'Fall', + 'school_year' => '2025-2026', + ]); + + DB::table('invoices')->insert([ + 'id' => 1, + 'parent_id' => 10, + 'invoice_number' => 'INV-1', + 'total_amount' => 200, + 'balance' => 200, + 'paid_amount' => 0, + 'has_discount' => 0, + 'issue_date' => '2025-01-01', + 'status' => 'unpaid', + 'school_year' => '2025-2026', + 'semester' => 'Fall', + ]); + + $service = new PaymentPlanService(); + $payment = $service->createPlan([ + 'parent_id' => 10, + 'invoice_id' => 1, + 'total_amount' => 200, + 'number_of_installments' => 2, + 'payment_date' => '2025-01-01', + 'payment_method' => 'cash', + ]); + + $this->assertSame('2025-2026', $payment->school_year); + $this->assertSame('Fall', $payment->semester); + $this->assertSame(200.0, (float) $payment->total_amount); + } +} diff --git a/tests/Unit/Services/Payments/PaymentTransactionServiceTest.php b/tests/Unit/Services/Payments/PaymentTransactionServiceTest.php new file mode 100644 index 00000000..aaf648ea --- /dev/null +++ b/tests/Unit/Services/Payments/PaymentTransactionServiceTest.php @@ -0,0 +1,67 @@ +string('payment_reference')->nullable(); + }); + } + + if (!Schema::hasColumn('payment_transactions', 'is_full_payment')) { + Schema::table('payment_transactions', function (Blueprint $table) { + $table->boolean('is_full_payment')->default(0); + }); + } + + DB::table('payments')->insert([ + 'id' => 1, + 'parent_id' => 10, + 'invoice_id' => 1, + 'total_amount' => 100, + 'paid_amount' => 0, + 'balance' => 100, + 'number_of_installments' => 1, + 'payment_method' => 'card', + 'payment_date' => '2025-01-01', + 'school_year' => '2025-2026', + 'status' => 'Pending', + ]); + + $service = new PaymentTransactionService(); + $transaction = $service->create([ + 'transaction_id' => 'TXN-1', + 'payment_id' => 1, + 'transaction_date' => '2025-01-02 00:00:00', + 'amount' => 50, + 'payment_method' => 'card', + 'payment_status' => 'Pending', + 'transaction_fee' => 1.25, + 'school_year' => '2025-2026', + 'semester' => 'Fall', + ]); + + $this->assertSame('TXN-1', $transaction->transaction_id); + + $updated = $service->updateStatus('TXN-1', 'Completed'); + + $this->assertTrue($updated); + $this->assertDatabaseHas('payment_transactions', [ + 'transaction_id' => 'TXN-1', + 'payment_status' => 'Completed', + ]); + } +} diff --git a/tests/Unit/Services/Payments/PaypalPaymentServiceTest.php b/tests/Unit/Services/Payments/PaypalPaymentServiceTest.php new file mode 100644 index 00000000..9bb032cc --- /dev/null +++ b/tests/Unit/Services/Payments/PaypalPaymentServiceTest.php @@ -0,0 +1,45 @@ +insert([ + 'id' => 1, + 'parent_id' => 10, + 'invoice_id' => 1, + 'total_amount' => 100, + 'paid_amount' => 0, + 'balance' => 100, + 'number_of_installments' => 1, + 'payment_method' => 'card', + 'payment_date' => '2025-01-01', + 'school_year' => '2025-2026', + 'status' => 'Pending', + ]); + + $service = new PaypalPaymentService(); + $result = $service->createPayment(1); + + $this->assertSame('hosted', $result['mode']); + $this->assertSame($service->getRedirectUrl(), $result['redirect_url']); + } + + public function test_execute_payment_throws_when_sdk_missing(): void + { + $service = new PaypalPaymentService(); + + $this->expectException(\RuntimeException::class); + + $service->executePayment('payer'); + } +} diff --git a/tests/Unit/Services/Payments/PaypalTransactionsServiceTest.php b/tests/Unit/Services/Payments/PaypalTransactionsServiceTest.php new file mode 100644 index 00000000..2e2759fb --- /dev/null +++ b/tests/Unit/Services/Payments/PaypalTransactionsServiceTest.php @@ -0,0 +1,46 @@ +insert([ + 'id' => 1, + 'transaction_id' => 'TXN-ABC', + 'payer_email' => 'payer@example.com', + 'event_type' => 'PAYMENT', + 'order_id' => 'ORDER-1', + 'parent_school_id' => 'P1', + 'amount' => 50, + 'currency' => 'USD', + 'created_at' => '2025-01-01 00:00:00', + ]); + + DB::table('paypal_payments')->insert([ + 'id' => 2, + 'transaction_id' => 'TXN-DEF', + 'payer_email' => 'other@example.com', + 'event_type' => 'REFUND', + 'order_id' => 'ORDER-2', + 'parent_school_id' => 'P2', + 'amount' => 75, + 'currency' => 'USD', + 'created_at' => '2025-01-02 00:00:00', + ]); + + $service = new PaypalTransactionsService(); + $results = $service->listAll('ABC'); + + $this->assertCount(1, $results); + $this->assertStringContainsString('TXN-ABC', json_encode($results)); + } +} diff --git a/tests/Unit/Services/Reimbursements/ReimbursementBatchAssignmentServiceTest.php b/tests/Unit/Services/Reimbursements/ReimbursementBatchAssignmentServiceTest.php new file mode 100644 index 00000000..7d4e2ee5 --- /dev/null +++ b/tests/Unit/Services/Reimbursements/ReimbursementBatchAssignmentServiceTest.php @@ -0,0 +1,83 @@ +insert([ + 'id' => 1, + 'school_id' => 1, + 'firstname' => 'Admin', + 'lastname' => 'User', + 'cellphone' => '5555555555', + 'email' => 'admin@example.com', + 'address_street' => '123 Main', + 'city' => 'City', + 'state' => 'ST', + 'zip' => '12345', + 'accept_school_policy' => 1, + 'is_verified' => 1, + 'status' => 'Active', + 'is_suspended' => 0, + 'failed_attempts' => 0, + 'password' => bcrypt('secret'), + 'semester' => 'Fall', + 'school_year' => '2025-2026', + ]); + + DB::table('reimbursement_batches')->insert([ + 'id' => 10, + 'title' => 'Batch 10', + 'status' => 'open', + 'school_year' => '2025-2026', + 'semester' => 'Fall', + 'opened_at' => now(), + ]); + + DB::table('expenses')->insert([ + 'id' => 100, + 'category' => 'Expense', + 'amount' => 25.00, + 'date_of_purchase' => '2025-01-01', + 'added_by' => 1, + 'purchased_by' => 1, + 'status' => 'approved', + ]); + + $service = new ReimbursementBatchAssignmentService(new ReimbursementLookupService()); + + $result = $service->updateAssignment(100, 10, 1, null, '2025-2026', 'Fall'); + $this->assertSame(10, $result['batch_id']); + + $this->assertDatabaseHas('reimbursement_batch_items', [ + 'batch_id' => 10, + 'expense_id' => 100, + 'admin_id' => 1, + 'school_year' => '2025-2026', + 'semester' => 'Fall', + ]); + + $service->updateAssignment(100, 0, null, null, '2025-2026', 'Fall'); + + $this->assertDatabaseHas('reimbursement_batch_items', [ + 'batch_id' => 10, + 'expense_id' => 100, + ]); + + $this->assertDatabaseMissing('reimbursement_batch_items', [ + 'batch_id' => 10, + 'expense_id' => 100, + 'unassigned_at' => null, + ]); + } +} diff --git a/tests/Unit/Services/Reimbursements/ReimbursementBatchServiceTest.php b/tests/Unit/Services/Reimbursements/ReimbursementBatchServiceTest.php new file mode 100644 index 00000000..632d2bed --- /dev/null +++ b/tests/Unit/Services/Reimbursements/ReimbursementBatchServiceTest.php @@ -0,0 +1,30 @@ +createBatch(null, 1, '2025-2026', 'Fall'); + + $this->assertSame(1, $batch['sequence']); + $this->assertStringContainsString('Batch', $batch['label']); + + $this->assertDatabaseHas('reimbursement_batches', [ + 'id' => $batch['batch_id'], + 'status' => 'open', + 'school_year' => '2025-2026', + 'semester' => 'Fall', + ]); + } +} diff --git a/tests/Unit/Services/Reimbursements/ReimbursementDonationServiceTest.php b/tests/Unit/Services/Reimbursements/ReimbursementDonationServiceTest.php new file mode 100644 index 00000000..a6a1cbcc --- /dev/null +++ b/tests/Unit/Services/Reimbursements/ReimbursementDonationServiceTest.php @@ -0,0 +1,77 @@ +insert([ + 'id' => 1, + 'school_id' => 1, + 'firstname' => 'Admin', + 'lastname' => 'User', + 'cellphone' => '5555555555', + 'email' => 'admin@example.com', + 'address_street' => '123 Main', + 'city' => 'City', + 'state' => 'ST', + 'zip' => '12345', + 'accept_school_policy' => 1, + 'is_verified' => 1, + 'status' => 'Active', + 'is_suspended' => 0, + 'failed_attempts' => 0, + 'password' => bcrypt('secret'), + 'semester' => 'Fall', + 'school_year' => '2025-2026', + ]); + + DB::table('expenses')->insert([ + 'id' => 200, + 'category' => 'Expense', + 'amount' => 40.00, + 'date_of_purchase' => '2025-02-01', + 'added_by' => 1, + 'purchased_by' => 1, + 'status' => 'approved', + ]); + + DB::table('reimbursement_batches')->insert([ + 'id' => 5, + 'title' => 'Batch 5', + 'status' => 'open', + 'school_year' => '2025-2026', + 'semester' => 'Fall', + 'opened_at' => now(), + ]); + + DB::table('reimbursement_batch_items')->insert([ + 'batch_id' => 5, + 'expense_id' => 200, + 'assigned_at' => now(), + ]); + + $service = new ReimbursementDonationService(); + $service->markDonation(200, 1); + + $this->assertDatabaseHas('expenses', [ + 'id' => 200, + 'category' => 'Donation', + 'status' => 'approved', + ]); + + $this->assertDatabaseMissing('reimbursement_batch_items', [ + 'batch_id' => 5, + 'expense_id' => 200, + 'unassigned_at' => null, + ]); + } +} diff --git a/tests/Unit/Services/Reimbursements/ReimbursementRecipientServiceTest.php b/tests/Unit/Services/Reimbursements/ReimbursementRecipientServiceTest.php new file mode 100644 index 00000000..a9665b59 --- /dev/null +++ b/tests/Unit/Services/Reimbursements/ReimbursementRecipientServiceTest.php @@ -0,0 +1,79 @@ +insert([ + [ + 'id' => 1, + 'school_id' => 1, + 'firstname' => 'Admin', + 'lastname' => 'User', + 'cellphone' => '5555555555', + 'email' => 'admin@example.com', + 'address_street' => '123 Main', + 'city' => 'City', + 'state' => 'ST', + 'zip' => '12345', + 'accept_school_policy' => 1, + 'is_verified' => 1, + 'status' => 'Active', + 'is_suspended' => 0, + 'failed_attempts' => 0, + 'password' => bcrypt('secret'), + 'semester' => 'Fall', + 'school_year' => '2025-2026', + ], + [ + 'id' => 2, + 'school_id' => 1, + 'firstname' => 'Parent', + 'lastname' => 'User', + 'cellphone' => '5555555555', + 'email' => 'parent@example.com', + 'address_street' => '123 Main', + 'city' => 'City', + 'state' => 'ST', + 'zip' => '12345', + 'accept_school_policy' => 1, + 'is_verified' => 1, + 'status' => 'Active', + 'is_suspended' => 0, + 'failed_attempts' => 0, + 'password' => bcrypt('secret'), + 'semester' => 'Fall', + 'school_year' => '2025-2026', + ], + ]); + + DB::table('roles')->insert([ + ['id' => 1, 'name' => 'admin'], + ['id' => 2, 'name' => 'parent'], + ]); + + DB::table('user_roles')->insert([ + ['user_id' => 1, 'role_id' => 1], + ['user_id' => 2, 'role_id' => 2], + ]); + + $service = new ReimbursementRecipientService(); + $recipients = $service->recipientOptions(); + + $ids = array_map(static fn ($row) => (int) ($row['id'] ?? 0), $recipients); + + $this->assertContains(1, $ids); + $this->assertNotContains(2, $ids); + $this->assertContains(990001, $ids); + $this->assertContains(990002, $ids); + } +} diff --git a/tests/Unit/Services/Scores/ExamScoreServiceTest.php b/tests/Unit/Services/Scores/ExamScoreServiceTest.php new file mode 100644 index 00000000..991e58a1 --- /dev/null +++ b/tests/Unit/Services/Scores/ExamScoreServiceTest.php @@ -0,0 +1,106 @@ +seedStudent(1, 'SCH1', 'Leo', 'Park'); + $this->seedStudentClass(1, 3, '2025-2026'); + + DB::table('midterm_exam')->insert([ + [ + 'student_id' => 1, + 'school_id' => 'SCH1', + 'class_section_id' => 3, + 'updated_by' => 99, + 'score' => 70, + 'semester' => 'Fall', + 'school_year' => '2025-2026', + ], + [ + 'student_id' => 1, + 'school_id' => 'SCH1', + 'class_section_id' => 3, + 'updated_by' => 99, + 'score' => 80, + 'semester' => 'Fall', + 'school_year' => '2025-2026', + ], + ]); + + $service = new ExamScoreService(new ScoreTermService()); + $result = $service->list('midterm_exam', 3, 'Fall', '2025-2026'); + + $this->assertCount(1, $result['students']); + $this->assertSame(80.0, $result['students'][0]['score']); + } + + public function test_update_creates_exam_score_and_missing_override(): void + { + $this->seedStudent(1, 'SCH1', 'Leo', 'Park'); + $this->seedStudentClass(1, 3, '2025-2026'); + + $service = new ExamScoreService(new ScoreTermService()); + + $count = $service->update( + 'midterm_exam', + 3, + 'Fall', + '2025-2026', + [1 => ['score' => 95]], + [1 => true], + 99 + ); + + $this->assertSame(1, $count); + $this->assertDatabaseHas('midterm_exam', [ + 'student_id' => 1, + 'class_section_id' => 3, + 'score' => 95.0, + ]); + $this->assertDatabaseHas('missing_score_overrides', [ + 'student_id' => 1, + 'class_section_id' => 3, + 'item_type' => 'midterm_exam', + 'item_index' => null, + 'is_allowed' => 1, + ]); + } + + private function seedStudent(int $id, string $schoolId, string $first, string $last): void + { + DB::table('students')->insert([ + 'id' => $id, + 'school_id' => $schoolId, + 'firstname' => $first, + 'lastname' => $last, + 'age' => 11, + 'gender' => 'M', + 'photo_consent' => 1, + 'parent_id' => 1, + 'year_of_registration' => '2025', + 'is_active' => 1, + ]); + } + + private function seedStudentClass(int $studentId, int $classSectionId, string $schoolYear): void + { + DB::table('student_class')->insert([ + 'student_id' => $studentId, + 'class_section_id' => $classSectionId, + 'school_year' => $schoolYear, + 'semester' => 'Fall', + 'is_event_only' => 0, + ]); + } +} diff --git a/tests/Unit/Services/Scores/HomeworkScoreServiceTest.php b/tests/Unit/Services/Scores/HomeworkScoreServiceTest.php new file mode 100644 index 00000000..95bba07d --- /dev/null +++ b/tests/Unit/Services/Scores/HomeworkScoreServiceTest.php @@ -0,0 +1,81 @@ +seedStudent(1, 'SCH1', 'Eli', 'Page'); + $this->seedStudentClass(1, 8, '2025-2026'); + + DB::table('homework')->insert([ + 'student_id' => 1, + 'school_id' => 'SCH1', + 'class_section_id' => 8, + 'updated_by' => 99, + 'homework_index' => 1, + 'score' => 91, + 'semester' => 'First Semester', + 'school_year' => '2025-2026', + ]); + + $service = new HomeworkScoreService(new ScoreTermService()); + $result = $service->list(8, 'Fall', '2025-2026'); + + $this->assertSame([1], $result['headers']); + $this->assertSame(91.0, $result['students'][0]['scores'][1]); + } + + public function test_add_column_inserts_next_index(): void + { + $this->seedStudent(1, 'SCH1', 'Eli', 'Page'); + $this->seedStudentClass(1, 8, '2025-2026'); + + $service = new HomeworkScoreService(new ScoreTermService()); + + $nextIndex = $service->addColumn(8, 'Fall', '2025-2026', 99); + + $this->assertSame(1, $nextIndex); + $this->assertDatabaseHas('homework', [ + 'student_id' => 1, + 'class_section_id' => 8, + 'homework_index' => 1, + ]); + } + + private function seedStudent(int $id, string $schoolId, string $first, string $last): void + { + DB::table('students')->insert([ + 'id' => $id, + 'school_id' => $schoolId, + 'firstname' => $first, + 'lastname' => $last, + 'age' => 10, + 'gender' => 'M', + 'photo_consent' => 1, + 'parent_id' => 1, + 'year_of_registration' => '2025', + 'is_active' => 1, + ]); + } + + private function seedStudentClass(int $studentId, int $classSectionId, string $schoolYear): void + { + DB::table('student_class')->insert([ + 'student_id' => $studentId, + 'class_section_id' => $classSectionId, + 'school_year' => $schoolYear, + 'semester' => 'Fall', + 'is_event_only' => 0, + ]); + } +} diff --git a/tests/Unit/Services/Scores/ParticipationScoreServiceTest.php b/tests/Unit/Services/Scores/ParticipationScoreServiceTest.php new file mode 100644 index 00000000..56829a8b --- /dev/null +++ b/tests/Unit/Services/Scores/ParticipationScoreServiceTest.php @@ -0,0 +1,98 @@ +seedStudent(1, 'SCH1', 'Owen', 'Hall'); + $this->seedStudentClass(1, 9, '2025-2026'); + + DB::table('participation')->insert([ + 'id' => 1, + 'student_id' => 1, + 'school_id' => 'SCH1', + 'class_section_id' => 9, + 'updated_by' => 99, + 'score' => 87, + 'semester' => 'Fall', + 'school_year' => '2025-2026', + ]); + + $service = new ParticipationScoreService(new ScoreTermService()); + $result = $service->list(9, 'Fall', '2025-2026'); + + $this->assertSame(87.0, $result['students'][0]['scores']['score']); + } + + public function test_update_updates_existing_scores(): void + { + $this->seedStudent(1, 'SCH1', 'Owen', 'Hall'); + $this->seedStudentClass(1, 9, '2025-2026'); + + DB::table('participation')->insert([ + 'id' => 1, + 'student_id' => 1, + 'school_id' => 'SCH1', + 'class_section_id' => 9, + 'updated_by' => 99, + 'score' => 70, + 'semester' => 'Fall', + 'school_year' => '2025-2026', + ]); + + $service = new ParticipationScoreService(new ScoreTermService()); + $count = $service->update( + 9, + 'Fall', + '2025-2026', + [1 => ['score' => 92]], + [], + 99 + ); + + $this->assertSame(1, $count); + $this->assertDatabaseHas('participation', [ + 'id' => 1, + 'student_id' => 1, + 'score' => 92.0, + ]); + } + + private function seedStudent(int $id, string $schoolId, string $first, string $last): void + { + DB::table('students')->insert([ + 'id' => $id, + 'school_id' => $schoolId, + 'firstname' => $first, + 'lastname' => $last, + 'age' => 10, + 'gender' => 'M', + 'photo_consent' => 1, + 'parent_id' => 1, + 'year_of_registration' => '2025', + 'is_active' => 1, + ]); + } + + private function seedStudentClass(int $studentId, int $classSectionId, string $schoolYear): void + { + DB::table('student_class')->insert([ + 'id' => 1, + 'student_id' => $studentId, + 'class_section_id' => $classSectionId, + 'school_year' => $schoolYear, + 'semester' => 'Fall', + 'is_event_only' => 0, + ]); + } +} diff --git a/tests/Unit/Services/Scores/ProjectScoreServiceTest.php b/tests/Unit/Services/Scores/ProjectScoreServiceTest.php new file mode 100644 index 00000000..b27c8f7b --- /dev/null +++ b/tests/Unit/Services/Scores/ProjectScoreServiceTest.php @@ -0,0 +1,114 @@ +seedStudent(1, 'SCH1', 'Maya', 'Stone'); + $this->seedStudentClass(1, 10, '2025-2026'); + + DB::table('project')->insert([ + 'student_id' => 1, + 'school_id' => 'SCH1', + 'class_section_id' => 10, + 'updated_by' => 99, + 'project_index' => 1, + 'score' => 88, + 'semester' => 'Fall', + 'school_year' => '2025-2026', + ]); + + $service = new ProjectScoreService(new ScoreTermService()); + $result = $service->list(10, 'Fall', '2025-2026'); + + $this->assertSame([1], $result['headers']); + $this->assertCount(1, $result['students']); + $this->assertSame(88.0, $result['students'][0]['scores'][1]); + } + + public function test_update_creates_scores_and_missing_overrides(): void + { + $this->seedStudent(1, 'SCH1', 'Maya', 'Stone'); + $this->seedStudentClass(1, 10, '2025-2026'); + + $service = new ProjectScoreService(new ScoreTermService()); + + $count = $service->update( + 10, + 'Fall', + '2025-2026', + [1 => [1 => '92']], + [1 => [1 => true]], + 99 + ); + + $this->assertSame(1, $count); + $this->assertDatabaseHas('project', [ + 'student_id' => 1, + 'class_section_id' => 10, + 'project_index' => 1, + 'score' => 92.0, + ]); + $this->assertDatabaseHas('missing_score_overrides', [ + 'student_id' => 1, + 'class_section_id' => 10, + 'item_type' => 'project', + 'item_index' => 1, + 'is_allowed' => 1, + ]); + } + + public function test_add_column_inserts_next_index(): void + { + $this->seedStudent(1, 'SCH1', 'Maya', 'Stone'); + $this->seedStudentClass(1, 10, '2025-2026'); + + $service = new ProjectScoreService(new ScoreTermService()); + + $nextIndex = $service->addColumn(10, 'Fall', '2025-2026', 99); + + $this->assertSame(1, $nextIndex); + $this->assertDatabaseHas('project', [ + 'student_id' => 1, + 'class_section_id' => 10, + 'project_index' => 1, + ]); + } + + private function seedStudent(int $id, string $schoolId, string $first, string $last): void + { + DB::table('students')->insert([ + 'id' => $id, + 'school_id' => $schoolId, + 'firstname' => $first, + 'lastname' => $last, + 'age' => 10, + 'gender' => 'M', + 'photo_consent' => 1, + 'parent_id' => 1, + 'year_of_registration' => '2025', + 'is_active' => 1, + ]); + } + + private function seedStudentClass(int $studentId, int $classSectionId, string $schoolYear): void + { + DB::table('student_class')->insert([ + 'student_id' => $studentId, + 'class_section_id' => $classSectionId, + 'school_year' => $schoolYear, + 'semester' => 'Fall', + 'is_event_only' => 0, + ]); + } +} diff --git a/tests/Unit/Services/Scores/QuizScoreServiceTest.php b/tests/Unit/Services/Scores/QuizScoreServiceTest.php new file mode 100644 index 00000000..2be158e8 --- /dev/null +++ b/tests/Unit/Services/Scores/QuizScoreServiceTest.php @@ -0,0 +1,114 @@ +seedStudent(1, 'SCH1', 'Nina', 'Kim'); + $this->seedStudentClass(1, 5, '2025-2026'); + + DB::table('quiz')->insert([ + 'student_id' => 1, + 'school_id' => 'SCH1', + 'class_section_id' => 5, + 'updated_by' => 99, + 'quiz_index' => 2, + 'score' => 77, + 'semester' => 'Fall', + 'school_year' => '2025-2026', + ]); + + $service = new QuizScoreService(new ScoreTermService()); + $result = $service->list(5, 'Fall', '2025-2026'); + + $this->assertSame([2], $result['headers']); + $this->assertCount(1, $result['students']); + $this->assertSame(77.0, $result['students'][0]['scores'][2]); + } + + public function test_update_creates_scores_and_missing_overrides(): void + { + $this->seedStudent(1, 'SCH1', 'Nina', 'Kim'); + $this->seedStudentClass(1, 5, '2025-2026'); + + $service = new QuizScoreService(new ScoreTermService()); + + $count = $service->update( + 5, + 'Fall', + '2025-2026', + [1 => [1 => '85']], + [1 => [1 => true]], + 99 + ); + + $this->assertSame(1, $count); + $this->assertDatabaseHas('quiz', [ + 'student_id' => 1, + 'class_section_id' => 5, + 'quiz_index' => 1, + 'score' => 85.0, + ]); + $this->assertDatabaseHas('missing_score_overrides', [ + 'student_id' => 1, + 'class_section_id' => 5, + 'item_type' => 'quiz', + 'item_index' => 1, + 'is_allowed' => 1, + ]); + } + + public function test_add_column_inserts_next_index(): void + { + $this->seedStudent(1, 'SCH1', 'Nina', 'Kim'); + $this->seedStudentClass(1, 5, '2025-2026'); + + $service = new QuizScoreService(new ScoreTermService()); + + $nextIndex = $service->addColumn(5, 'Fall', '2025-2026', 99); + + $this->assertSame(1, $nextIndex); + $this->assertDatabaseHas('quiz', [ + 'student_id' => 1, + 'class_section_id' => 5, + 'quiz_index' => 1, + ]); + } + + private function seedStudent(int $id, string $schoolId, string $first, string $last): void + { + DB::table('students')->insert([ + 'id' => $id, + 'school_id' => $schoolId, + 'firstname' => $first, + 'lastname' => $last, + 'age' => 10, + 'gender' => 'F', + 'photo_consent' => 1, + 'parent_id' => 1, + 'year_of_registration' => '2025', + 'is_active' => 1, + ]); + } + + private function seedStudentClass(int $studentId, int $classSectionId, string $schoolYear): void + { + DB::table('student_class')->insert([ + 'student_id' => $studentId, + 'class_section_id' => $classSectionId, + 'school_year' => $schoolYear, + 'semester' => 'Fall', + 'is_event_only' => 0, + ]); + } +} diff --git a/tests/Unit/Services/Scores/ScoreCommentServiceTest.php b/tests/Unit/Services/Scores/ScoreCommentServiceTest.php new file mode 100644 index 00000000..63a13f20 --- /dev/null +++ b/tests/Unit/Services/Scores/ScoreCommentServiceTest.php @@ -0,0 +1,80 @@ +seedStudent(1, 'SCH1', 'Ava', 'Lane'); + $this->seedStudentClass(1, 2, '2025-2026'); + + DB::table('score_comments')->insert([ + 'student_id' => 1, + 'class_section_id' => 2, + 'score_type' => 'midterm', + 'semester' => 'Fall', + 'school_year' => '2025-2026', + 'comment' => 'Ava has shown consistent improvement and strong effort across assignments.', + ]); + + $service = new ScoreCommentService(new ScoreTermService()); + $result = $service->list('2', 'Fall', '2025-2026'); + + $this->assertCount(1, $result['students']); + $this->assertSame('Ava', $result['students'][0]['firstname']); + $this->assertSame('midterm', array_key_first($result['comments'][1])); + } + + public function test_save_returns_validation_errors_for_short_comment(): void + { + $this->seedStudent(1, 'SCH1', 'Ava', 'Lane'); + + $service = new ScoreCommentService(new ScoreTermService()); + $result = $service->save( + 2, + 'Fall', + '2025-2026', + [1 => ['midterm' => 'Too short']], + [], + 99 + ); + + $this->assertNotEmpty($result['errors']); + } + + private function seedStudent(int $id, string $schoolId, string $first, string $last): void + { + DB::table('students')->insert([ + 'id' => $id, + 'school_id' => $schoolId, + 'firstname' => $first, + 'lastname' => $last, + 'age' => 10, + 'gender' => 'F', + 'photo_consent' => 1, + 'parent_id' => 1, + 'year_of_registration' => '2025', + 'is_active' => 1, + ]); + } + + private function seedStudentClass(int $studentId, int $classSectionId, string $schoolYear): void + { + DB::table('student_class')->insert([ + 'student_id' => $studentId, + 'class_section_id' => $classSectionId, + 'school_year' => $schoolYear, + 'semester' => 'Fall', + 'is_event_only' => 0, + ]); + } +} diff --git a/tests/Unit/Services/Scores/ScoreDashboardServiceTest.php b/tests/Unit/Services/Scores/ScoreDashboardServiceTest.php new file mode 100644 index 00000000..bc773bcc --- /dev/null +++ b/tests/Unit/Services/Scores/ScoreDashboardServiceTest.php @@ -0,0 +1,110 @@ +seedClassSection(10, '1A'); + $this->seedStudent(1, 'SCH1', 'Noah', 'West', 5); + $this->seedStudentClass(1, 10, '2025-2026'); + + DB::table('teacher_class')->insert([ + 'teacher_id' => 1, + 'class_section_id' => 10, + 'semester' => 'Fall', + 'school_year' => '2025-2026', + ]); + + DB::table('semester_scores')->insert([ + 'student_id' => 1, + 'school_id' => 'SCH1', + 'class_section_id' => 10, + 'homework_avg' => 85, + 'quiz_avg' => 90, + 'project_avg' => 92, + 'attendance_score' => 95, + 'ptap_score' => 90, + 'semester_score' => 91, + 'semester' => 'Fall', + 'school_year' => '2025-2026', + ]); + + $service = new ScoreDashboardService(new ScoreTermService()); + $result = $service->overview(1, null, 'Fall', '2025-2026'); + + $this->assertSame(10, $result['class_section_id']); + $this->assertSame('1A', $result['class_section_name']); + $this->assertSame(85.0, $result['students'][0]['scores']['homework']); + } + + public function test_view_student_scores_groups_by_year_and_semester(): void + { + $this->seedClassSection(10, '1A'); + $this->seedStudent(2, 'SCH2', 'Mia', 'Clark', 7); + $this->seedStudentClass(2, 10, '2025-2026'); + + DB::table('semester_scores')->insert([ + 'student_id' => 2, + 'school_id' => 'SCH2', + 'class_section_id' => 10, + 'homework_avg' => 80, + 'semester' => 'Fall', + 'school_year' => '2025-2026', + ]); + + $service = new ScoreDashboardService(new ScoreTermService()); + $result = $service->viewStudentScores(7, '2025-2026'); + + $this->assertArrayHasKey('2025-2026', $result['scores']); + $this->assertArrayHasKey('Fall', $result['scores']['2025-2026']); + $this->assertArrayHasKey(2, $result['scores']['2025-2026']['Fall']); + } + + private function seedStudent(int $id, string $schoolId, string $first, string $last, int $parentId): void + { + DB::table('students')->insert([ + 'id' => $id, + 'school_id' => $schoolId, + 'firstname' => $first, + 'lastname' => $last, + 'age' => 12, + 'gender' => 'F', + 'photo_consent' => 1, + 'parent_id' => $parentId, + 'year_of_registration' => '2025', + 'is_active' => 1, + ]); + } + + private function seedStudentClass(int $studentId, int $classSectionId, string $schoolYear): void + { + DB::table('student_class')->insert([ + 'student_id' => $studentId, + 'class_section_id' => $classSectionId, + 'school_year' => $schoolYear, + 'semester' => 'Fall', + 'is_event_only' => 0, + ]); + } + + private function seedClassSection(int $classSectionId, string $name): void + { + DB::table('classSection')->insert([ + 'class_section_id' => $classSectionId, + 'class_section_name' => $name, + 'class_id' => 1, + 'semester' => 'Fall', + 'school_year' => '2025-2026', + ]); + } +} diff --git a/tests/Unit/Services/Scores/ScorePredictorServiceTest.php b/tests/Unit/Services/Scores/ScorePredictorServiceTest.php new file mode 100644 index 00000000..fca60523 --- /dev/null +++ b/tests/Unit/Services/Scores/ScorePredictorServiceTest.php @@ -0,0 +1,77 @@ +insert([ + ['id' => 1, 'config_key' => 'school_year', 'config_value' => '2025-2026'], + ['id' => 2, 'config_key' => 'semester', 'config_value' => 'Fall'], + ['id' => 3, 'config_key' => 'trophy_score', 'config_value' => '94'], + ['id' => 4, 'config_key' => 'pass_score', 'config_value' => '60'], + ]); + + DB::table('classSection')->insert([ + 'id' => 1, + 'class_section_id' => 1, + 'class_section_name' => '1A', + 'class_id' => 1, + 'semester' => 'Fall', + 'school_year' => '2025-2026', + ]); + + DB::table('students')->insert([ + 'id' => 100, + 'parent_id' => 10, + 'firstname' => 'Kid', + 'lastname' => 'User', + 'school_id' => 1, + 'age' => 10, + 'gender' => 'M', + 'photo_consent' => 1, + 'year_of_registration' => '2025', + 'is_active' => 1, + ]); + + DB::table('student_class')->insert([ + 'id' => 1, + 'student_id' => 100, + 'class_section_id' => 1, + 'school_year' => '2025-2026', + 'semester' => 'Fall', + 'is_event_only' => 0, + ]); + + DB::table('semester_scores')->insert([ + 'id' => 1, + 'student_id' => 100, + 'school_id' => 1, + 'class_section_id' => 1, + 'semester' => 'fall', + 'school_year' => '2025-2026', + 'semester_score' => 92, + ]); + + $service = new ScorePredictorService( + new ScorePredictorDataService(), + new ScorePredictorRiskService() + ); + + $result = $service->report('2025-2026', 1); + + $this->assertSame('2025-2026', $result['school_year']); + $this->assertCount(1, $result['students']); + $this->assertSame(100, $result['students'][0]['student_id']); + } +} diff --git a/tests/Unit/Services/Scores/ScoreTermServiceTest.php b/tests/Unit/Services/Scores/ScoreTermServiceTest.php new file mode 100644 index 00000000..8c3a6093 --- /dev/null +++ b/tests/Unit/Services/Scores/ScoreTermServiceTest.php @@ -0,0 +1,38 @@ +insert([ + ['config_key' => 'semester', 'config_value' => 'Fall'], + ['config_key' => 'school_year', 'config_value' => '2025-2026'], + ]); + + $service = new ScoreTermService(); + + $this->assertSame('Fall', $service->semesterLabel(null)); + $this->assertSame('2025-2026', $service->schoolYear(null)); + } + + public function test_normalize_and_variants(): void + { + $service = new ScoreTermService(); + + $this->assertSame('fall', $service->normalizeSemester('Semester 1')); + $this->assertSame('spring', $service->normalizeSemester('2nd Semester')); + + $variants = $service->semesterVariants('Fall'); + $this->assertContains('First Semester', $variants); + $this->assertContains('Semester 1', $variants); + } +} diff --git a/tests/Unit/Services/Scores/SemesterScoreServiceTest.php b/tests/Unit/Services/Scores/SemesterScoreServiceTest.php new file mode 100644 index 00000000..c699cf76 --- /dev/null +++ b/tests/Unit/Services/Scores/SemesterScoreServiceTest.php @@ -0,0 +1,107 @@ +insert([ + 'student_id' => 1, + 'school_id' => 'SCH1', + 'class_section_id' => 4, + 'updated_by' => 99, + 'score' => 88, + 'semester' => 'Fall', + 'school_year' => '2025-2026', + ]); + + $service = new SemesterScoreService( + new FakeAttendanceCalculator(), + new FakeHomeworkCalculator(), + new FakeQuizCalculator(), + new FakeProjectCalculator() + ); + + $service->updateStudentScores([ + 'student_id' => 1, + 'school_id' => 'SCH1', + 'class_section_id' => 4, + 'updated_by' => 99, + ], 'Fall', '2025-2026'); + + $row = DB::table('semester_scores')->where([ + 'student_id' => 1, + 'class_section_id' => 4, + 'semester' => 'Fall', + 'school_year' => '2025-2026', + ])->first(); + + $this->assertNotNull($row); + $this->assertSame(95.0, (float) $row->attendance_score); + $this->assertSame(80.0, (float) $row->homework_avg); + $this->assertSame(90.0, (float) $row->quiz_avg); + $this->assertSame(100.0, (float) $row->project_avg); + $this->assertSame(84.0, (float) $row->ptap_score); + $this->assertSame(88.0, (float) $row->midterm_exam_score); + $this->assertSame(88.6, (float) $row->semester_score); + } +} + +class FakeAttendanceCalculator extends AttendanceCalculator +{ + public function __construct() + { + } + + public function calculate(int $studentId, string $semester, string $schoolYear, ?int $classSectionId = null): array + { + return ['attendance_score' => 95.0]; + } +} + +class FakeHomeworkCalculator extends HomeworkCalculator +{ + public function __construct() + { + } + + public function calculate(int $studentId, string $semester, string $schoolYear, ?int $classSectionId = null): array + { + return ['homework_avg' => 80.0]; + } +} + +class FakeQuizCalculator extends QuizCalculator +{ + public function calculate(int $studentId, string $semester, string $schoolYear, ?int $classSectionId = null): array + { + return ['quiz_avg' => 90.0]; + } +} + +class FakeProjectCalculator extends ProjectCalculator +{ + public function __construct() + { + } + + public function calculate(int $studentId, string $semester, string $schoolYear, ?int $classSectionId = null): array + { + return [ + 'project_avg' => 100.0, + 'participation_score' => 70.0, + ]; + } +} diff --git a/tests/Unit/Services/Semesters/SemesterConfigServiceTest.php b/tests/Unit/Services/Semesters/SemesterConfigServiceTest.php new file mode 100644 index 00000000..9f1976e5 --- /dev/null +++ b/tests/Unit/Services/Semesters/SemesterConfigServiceTest.php @@ -0,0 +1,27 @@ +insert([ + ['id' => 1, 'config_key' => 'fall_semester_start', 'config_value' => '2025-09-01'], + ['id' => 2, 'config_key' => 'spring_semester_start', 'config_value' => '2026-01-20'], + ['id' => 3, 'config_key' => 'last_school_day', 'config_value' => '2026-06-01'], + ]); + + $service = new SemesterConfigService(); + $this->assertSame('2025-09-01', $service->getFallStart()?->format('Y-m-d')); + $this->assertSame('2026-01-20', $service->getSpringStart()?->format('Y-m-d')); + $this->assertSame('2026-06-01', $service->getLastSchoolDay()?->format('Y-m-d')); + } +} diff --git a/tests/Unit/Services/Semesters/SemesterRangeServiceTest.php b/tests/Unit/Services/Semesters/SemesterRangeServiceTest.php new file mode 100644 index 00000000..58400bff --- /dev/null +++ b/tests/Unit/Services/Semesters/SemesterRangeServiceTest.php @@ -0,0 +1,46 @@ +getSchoolYearRange('2025-2026'); + + $this->assertSame('2025-09-01', $start); + $this->assertSame('2026-06-30', $end); + } + + public function test_get_semester_for_date_uses_config_dates(): void + { + DB::table('configuration')->insert([ + ['id' => 1, 'config_key' => 'fall_semester_start', 'config_value' => '2025-09-01'], + ['id' => 2, 'config_key' => 'spring_semester_start', 'config_value' => '2026-01-20'], + ['id' => 3, 'config_key' => 'last_school_day', 'config_value' => '2026-06-01'], + ]); + + $service = new SemesterRangeService(new SemesterConfigService()); + $semester = $service->getSemesterForDate('2025-10-01'); + + $this->assertSame('Fall', $semester); + } + + public function test_normalize_semester(): void + { + $service = new SemesterRangeService(new SemesterConfigService()); + + $this->assertSame('Fall', $service->normalizeSemester('fall')); + $this->assertSame('Spring', $service->normalizeSemester('SPRING')); + $this->assertSame('', $service->normalizeSemester('winter')); + } +} diff --git a/tests/Unit/Services/Users/LoginActivityServiceTest.php b/tests/Unit/Services/Users/LoginActivityServiceTest.php new file mode 100644 index 00000000..12f899a5 --- /dev/null +++ b/tests/Unit/Services/Users/LoginActivityServiceTest.php @@ -0,0 +1,45 @@ +insert([ + 'user_id' => 1, + 'email' => 'user@example.com', + 'login_time' => '2025-01-02 09:00:00', + 'logout_time' => null, + 'ip_address' => '10.0.0.1', + 'user_agent' => 'TestAgent', + 'semester' => 'Fall', + 'school_year' => '2025-2026', + ]); + + DB::table('login_activity')->insert([ + 'user_id' => 1, + 'email' => 'user@example.com', + 'login_time' => '2025-01-01 09:00:00', + 'logout_time' => null, + 'ip_address' => '10.0.0.2', + 'user_agent' => 'TestAgent', + 'semester' => 'Fall', + 'school_year' => '2025-2026', + ]); + + $service = app(LoginActivityService::class); + $payload = $service->list(1, 2); + + $this->assertSame(2, $payload['pagination']['total']); + $this->assertSame(2, $payload['pagination']['currentPage']); + $this->assertSame('10.0.0.2', $payload['activities'][0]['ip_address']); + } +} diff --git a/tests/Unit/Services/Users/UserEventServiceTest.php b/tests/Unit/Services/Users/UserEventServiceTest.php new file mode 100644 index 00000000..abb925b8 --- /dev/null +++ b/tests/Unit/Services/Users/UserEventServiceTest.php @@ -0,0 +1,52 @@ +shouldReceive('send') + ->once() + ->with( + 'user@example.com', + 'Welcome to Al Rahma Sunday School!', + Mockery::type('string'), + 'registration' + ) + ->andReturn(true); + + $service = new UserEventService($emailService); + + $result = $service->handleNewAccountAdded([ + 'firstname' => 'Test', + 'lastname' => 'User', + 'email' => 'user@example.com', + ]); + + $this->assertTrue($result); + } + + public function test_handle_new_account_requires_email(): void + { + $emailService = Mockery::mock(EmailService::class); + $emailService->shouldNotReceive('send'); + + $service = new UserEventService($emailService); + $result = $service->handleNewAccountAdded([ + 'firstname' => 'Test', + 'lastname' => 'User', + ]); + + $this->assertFalse($result); + } +} diff --git a/tests/Unit/Services/Users/UserListServiceTest.php b/tests/Unit/Services/Users/UserListServiceTest.php new file mode 100644 index 00000000..565d7a42 --- /dev/null +++ b/tests/Unit/Services/Users/UserListServiceTest.php @@ -0,0 +1,73 @@ +insert([ + 'id' => 5, + 'name' => 'Parent', + 'slug' => 'parent', + 'description' => 'Parent role', + 'dashboard_route' => 'parent/dashboard', + 'priority' => 1, + 'is_active' => 1, + ]); + + DB::table('users')->insert([ + 'id' => 1, + 'school_id' => 1, + 'firstname' => 'Jane', + 'lastname' => 'Doe', + 'cellphone' => '5551112222', + 'email' => 'second.parent@example.com', + 'address_street' => '123 Main', + 'city' => 'City', + 'state' => 'ST', + 'zip' => '12345', + 'accept_school_policy' => 1, + 'is_verified' => 1, + 'status' => 'Active', + 'is_suspended' => 0, + 'failed_attempts' => 0, + 'password' => bcrypt('secret'), + 'semester' => 'Fall', + 'school_year' => '2025-2026', + ]); + + DB::table('user_roles')->insert([ + 'user_id' => 1, + 'role_id' => 5, + ]); + + DB::table('parents')->insert([ + 'secondparent_firstname' => 'Jane', + 'secondparent_lastname' => 'Doe', + 'secondparent_gender' => 'Female', + 'secondparent_email' => 'second.parent@example.com', + 'secondparent_phone' => '5551112222', + 'firstparent_id' => 10, + 'secondparent_id' => 11, + 'semester' => 'Fall', + 'school_year' => '2025-2026', + ]); + + $service = app(UserListService::class); + $rows = $service->list('id', 'asc'); + + $this->assertCount(1, $rows); + $this->assertSame('second.parent@example.com', $rows[0]['user']['email']); + $this->assertSame(['Parent'], $rows[0]['roles']); + $this->assertSame('Yes', $rows[0]['second_from_parents']); + } +} diff --git a/tests/Unit/Services/Users/UserManagementServiceTest.php b/tests/Unit/Services/Users/UserManagementServiceTest.php new file mode 100644 index 00000000..a8eb0782 --- /dev/null +++ b/tests/Unit/Services/Users/UserManagementServiceTest.php @@ -0,0 +1,153 @@ +insert([ + 'id' => 1, + 'name' => 'Parent', + 'slug' => 'parent', + 'description' => 'Parent role', + 'dashboard_route' => 'parent/dashboard', + 'priority' => 1, + 'is_active' => 1, + ]); + + $eventService = Mockery::mock(UserEventService::class); + $eventService->shouldReceive('handleNewAccountAdded') + ->once() + ->andReturn(true); + + $service = new UserManagementService($eventService); + + $result = $service->create([ + 'firstname' => 'New', + 'lastname' => 'User', + 'cellphone' => '5554443333', + 'email' => 'new.user@example.com', + 'address_street' => '123 Main', + 'city' => 'City', + 'state' => 'ST', + 'zip' => '12345', + 'accept_school_policy' => true, + 'role_id' => 1, + 'password' => 'secret123', + 'semester' => 'Fall', + 'school_year' => '2025-2026', + ]); + + $this->assertTrue($result['ok']); + $this->assertInstanceOf(User::class, $result['user']); + $this->assertDatabaseHas('users', [ + 'email' => 'new.user@example.com', + 'firstname' => 'New', + 'lastname' => 'User', + ]); + $this->assertDatabaseHas('user_roles', [ + 'user_id' => $result['user']->id, + 'role_id' => 1, + ]); + } + + public function test_update_changes_user_fields(): void + { + DB::table('users')->insert([ + 'id' => 1, + 'school_id' => 1, + 'firstname' => 'Test', + 'lastname' => 'User', + 'cellphone' => '5555555555', + 'email' => 'test@example.com', + 'address_street' => '123 Main', + 'city' => 'City', + 'state' => 'ST', + 'zip' => '12345', + 'accept_school_policy' => 1, + 'is_verified' => 1, + 'status' => 'Active', + 'is_suspended' => 0, + 'failed_attempts' => 0, + 'password' => bcrypt('secret'), + 'semester' => 'Fall', + 'school_year' => '2025-2026', + ]); + + $eventService = Mockery::mock(UserEventService::class); + $service = new UserManagementService($eventService); + + $result = $service->update(1, [ + 'firstname' => 'Updated', + 'email' => 'updated@example.com', + 'accept_school_policy' => false, + ]); + + $this->assertTrue($result['ok']); + $this->assertDatabaseHas('users', [ + 'id' => 1, + 'firstname' => 'Updated', + 'email' => 'updated@example.com', + 'accept_school_policy' => 0, + ]); + } + + public function test_delete_removes_user_and_roles(): void + { + DB::table('users')->insert([ + 'id' => 1, + 'school_id' => 1, + 'firstname' => 'Test', + 'lastname' => 'User', + 'cellphone' => '5555555555', + 'email' => 'test@example.com', + 'address_street' => '123 Main', + 'city' => 'City', + 'state' => 'ST', + 'zip' => '12345', + 'accept_school_policy' => 1, + 'is_verified' => 1, + 'status' => 'Active', + 'is_suspended' => 0, + 'failed_attempts' => 0, + 'password' => bcrypt('secret'), + 'semester' => 'Fall', + 'school_year' => '2025-2026', + ]); + + DB::table('roles')->insert([ + 'id' => 1, + 'name' => 'Parent', + 'slug' => 'parent', + 'description' => 'Parent role', + 'dashboard_route' => 'parent/dashboard', + 'priority' => 1, + 'is_active' => 1, + ]); + + DB::table('user_roles')->insert([ + 'user_id' => 1, + 'role_id' => 1, + ]); + + $eventService = Mockery::mock(UserEventService::class); + $service = new UserManagementService($eventService); + + $result = $service->delete(1); + + $this->assertTrue($result['ok']); + $this->assertDatabaseMissing('users', ['id' => 1]); + $this->assertDatabaseMissing('user_roles', ['user_id' => 1]); + } +}