validateRequest(['GET']); if (!$validation['valid']) { http_response_code($validation['http_code']); header('Content-Type: application/json'); echo json_encode(['status' => 'error', 'message' => $validation['error']]); exit; } // 2. Validate Input Parameters $studentId = trim($_GET['student_id'] ?? ''); if (empty($studentId)) { http_response_code(400); header('Content-Type: application/json'); echo json_encode(['status' => 'error', 'message' => 'Student ID is required']); exit; } // 3. Validate Student Exists $studentCheck = $validator->validateStudentExists($studentId); if (!$studentCheck['valid']) { http_response_code($studentCheck['http_code']); header('Content-Type: application/json'); echo json_encode(['status' => 'error', 'message' => $studentCheck['error']]); exit; } // Prevent output from messing up image headers ob_start(); ini_set('display_errors', 0); error_reporting(E_ALL & ~E_DEPRECATED & ~E_NOTICE); try { // 4. Fetch Last Payment Receipt Number $sqlLastReceipt = "SELECT receipt_no, payment_date FROM tb_account_payment_registers WHERE student_id = :student_id ORDER BY payment_date DESC, id DESC LIMIT 1"; $stmt = $pdo->prepare($sqlLastReceipt); $stmt->execute(['student_id' => $studentId]); $lastPayment = $stmt->fetch(PDO::FETCH_ASSOC); if (!$lastPayment) { http_response_code(404); header('Content-Type: application/json'); echo json_encode([ 'status' => 'error', 'message' => 'No payment records found for this student' ]); exit; } $receiptNo = $lastPayment['receipt_no']; $paymentDate = $lastPayment['payment_date']; // 5. Fetch Receipt Meta Info (Student, Date) $sqlInfo = "SELECT pr.student_id, pr.payment_date, sr.last_name, sr.first_name, sr.other_name, sr.student_code, al.level_name FROM tb_account_payment_registers pr JOIN tb_student_registrations sr ON pr.student_id = sr.id LEFT JOIN tb_academic_levels al ON sr.level_id = al.id WHERE pr.receipt_no = :receipt_no LIMIT 1"; $stmt = $pdo->prepare($sqlInfo); $stmt->execute(['receipt_no' => $receiptNo]); $receiptInfo = $stmt->fetch(PDO::FETCH_ASSOC); if (!$receiptInfo) { http_response_code(500); header('Content-Type: application/json'); echo json_encode([ 'status' => 'error', 'message' => 'Receipt information not found' ]); exit; } $studentId = $receiptInfo['student_id']; // 6. Fetch All Fees for Student (from receivables) $sqlFees = "SELECT ar.fee_id, ar.actual_value as amount_billed, ar.academic_session, ar.term_of_session, sf.description as fee_description FROM tb_account_receivables ar JOIN tb_account_school_fees sf ON ar.fee_id = sf.id WHERE ar.student_id = :sid ORDER BY ar.academic_session ASC, ar.term_of_session ASC"; $stmtFees = $pdo->prepare($sqlFees); $stmtFees->execute(['sid' => $studentId]); $allFees = $stmtFees->fetchAll(PDO::FETCH_ASSOC); $allocations = []; $receiptTotalPaid = 0; foreach ($allFees as $fee) { // Calculate Paid To Date (up to this receipt's date) $sqlPaid = "SELECT SUM(amount_paid) as total_paid FROM tb_account_payment_registers WHERE student_id = :sid AND fee_id = :fid AND academic_session = :as AND term_of_session = :ts AND payment_date <= :pd"; $stmtPaid = $pdo->prepare($sqlPaid); $stmtPaid->execute([ 'sid' => $studentId, 'fid' => $fee['fee_id'], 'as' => $fee['academic_session'], 'ts' => $fee['term_of_session'], 'pd' => $paymentDate ]); $paidResult = $stmtPaid->fetch(PDO::FETCH_ASSOC); $paidToDate = floatval($paidResult['total_paid'] ?? 0); // Calculate Amount paid IN THIS RECEIPT (for total calculation) $sqlReceiptPay = "SELECT SUM(amount_paid) as receipt_paid FROM tb_account_payment_registers WHERE receipt_no = :rno AND fee_id = :fid AND academic_session = :as AND term_of_session = :ts"; $stmtReceiptPay = $pdo->prepare($sqlReceiptPay); $stmtReceiptPay->execute([ 'rno' => $receiptNo, 'fid' => $fee['fee_id'], 'as' => $fee['academic_session'], 'ts' => $fee['term_of_session'] ]); $receiptPayResult = $stmtReceiptPay->fetch(PDO::FETCH_ASSOC); $paidInReceipt = floatval($receiptPayResult['receipt_paid'] ?? 0); $receiptTotalPaid += $paidInReceipt; $balance = floatval($fee['amount_billed']) - $paidToDate; // Condition: Show if (Balance > 0) OR (PaidInReceipt > 0) // Helps filter out old fully paid fees, but keeps current payments even if they zeroed the balance if ($balance > 0.001 || $paidInReceipt > 0.001) { $allocations[] = [ 'description' => $fee['fee_description'], 'academic_session' => $fee['academic_session'], 'term_of_session' => $fee['term_of_session'], 'amount_billed' => floatval($fee['amount_billed']), 'amount' => $paidInReceipt, 'total_paid_to_date' => $paidToDate, 'balance' => $balance ]; } } // 7. Prepare data structure for generator $data = [ 'receipt_no' => $receiptNo, 'student_name' => trim($receiptInfo['last_name'] . ' ' . $receiptInfo['first_name'] . ' ' . ($receiptInfo['other_name'] ?? '')), 'student_code' => $receiptInfo['student_code'], 'level_name' => $receiptInfo['level_name'] ?? '', 'payment_date' => $paymentDate, 'total_paid' => $receiptTotalPaid, 'allocations' => $allocations ]; // 8. Generate Image $generator = new ReceiptGenerator(); $imageData = $generator->generate($data); // 9. Output Image ob_end_clean(); // Discard any warnings/output buffered so far header('Content-Type: image/png'); header('Content-Disposition: inline; filename="receipt_' . $receiptNo . '.png"'); header('Content-Length: ' . strlen($imageData)); echo $imageData; } catch (Exception $e) { ob_end_clean(); http_response_code(500); header('Content-Type: application/json'); echo json_encode([ 'status' => 'error', 'message' => 'Error generating receipt: ' . $e->getMessage() ]); }