Spaces:
Running
Running
| class ReceiptGenerator | |
| { | |
| private $fontPath; | |
| private $logoPath; | |
| public function __construct() | |
| { | |
| if (!extension_loaded('gd')) { | |
| throw new Exception("The PHP GD extension is not enabled. Please enable it in your php.ini or install php-gd."); | |
| } | |
| // Try to find a suitable font | |
| $fonts = [ | |
| 'C:/Windows/Fonts/arial.ttf', | |
| '/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf', | |
| '/usr/share/fonts/truetype/liberation/LiberationSans-Regular.ttf', | |
| '/usr/share/fonts/TTF/arial.ttf' | |
| ]; | |
| $this->fontPath = null; | |
| foreach ($fonts as $font) { | |
| if (file_exists($font)) { | |
| $this->fontPath = $font; | |
| break; | |
| } | |
| } | |
| $this->logoPath = __DIR__ . '/../assets/logo.png'; | |
| } | |
| public function generate($data) | |
| { | |
| $width = 900; // Increased width to accommodate 6 columns comfortably | |
| // Calculate dynamic height | |
| $baseHeight = 450; | |
| $rowHeight = 40; | |
| $numRows = count($data['allocations'] ?? []); | |
| $height = $baseHeight + ($numRows * $rowHeight) + 150; | |
| $image = imagecreatetruecolor($width, $height); | |
| // Colors | |
| $white = imagecolorallocate($image, 255, 255, 255); | |
| $black = imagecolorallocate($image, 0, 0, 0); | |
| $grey = imagecolorallocate($image, 100, 100, 100); | |
| $lightGrey = imagecolorallocate($image, 240, 240, 240); | |
| imagefilledrectangle($image, 0, 0, $width, $height, $white); | |
| // Logo | |
| if (file_exists($this->logoPath)) { | |
| $logo = @imagecreatefrompng($this->logoPath); | |
| if ($logo) { | |
| $logoW = imagesx($logo); | |
| $logoH = imagesy($logo); | |
| $targetW = 100; | |
| $targetH = ($targetW / $logoW) * $logoH; | |
| imagecopyresampled($image, $logo, 40, 30, 0, 0, (int) $targetW, (int) $targetH, (int) $logoW, (int) $logoH); | |
| } | |
| } | |
| // School Name Logic | |
| $schoolName = "ACE JUNIOR COLLEGE"; | |
| $levelName = isset($data['level_name']) ? strtoupper($data['level_name']) : ''; | |
| // Check for Senior classes (SSS 1, SSS 2, SSS 3, or Senior) | |
| if (strpos($levelName, 'SSS') !== false || strpos($levelName, 'SENIOR') !== false) { | |
| $schoolName = "ACE SENIOR COLLEGE"; | |
| } | |
| // Header | |
| $this->centerText($image, 18, 50, $schoolName, $black); | |
| $this->centerText($image, 10, 75, "...education for excellence", $grey); | |
| $this->centerText($image, 10, 100, "1, Ebute - Igbogbo Road, Ipakodo, Ikorodu, Lagos, Nigeria", $black); | |
| $this->centerText($image, 10, 120, "Phone No(s): 08027449739, 08077275777", $black); | |
| $this->centerText($image, 10, 140, "mailto:info@acecollege.com.ng https://www.acecollege.com.ng", $black); | |
| // Receipt Title | |
| $titleY = 180; | |
| $receiptNo = $data['receipt_no'] ?? 'N/A'; | |
| $this->centerText($image, 12, $titleY, "SCHOOL FEES RECEIPT - REFERENCE NO: $receiptNo", $black); | |
| // Session Info | |
| $session = $data['allocations'][0]['academic_session'] ?? '----'; | |
| $term = $data['allocations'][0]['term_of_session'] ?? '-'; | |
| $nextSession = is_numeric($session) ? ($session + 1) : ''; | |
| $levelPart = isset($data['level_name']) ? ' - ' . strtoupper($data['level_name']) : ''; | |
| $sessionStr = "$session/$nextSession ACADEMIC SESSION, TERM $term" . $levelPart; | |
| $this->centerText($image, 10, $titleY + 25, $sessionStr, $grey); | |
| // Student Box | |
| $boxY = 230; | |
| imagerectangle($image, 40, $boxY, $width - 40, $boxY + 40, $grey); | |
| // We expect student_name to be passed in $data. If not, fallback. | |
| $studentName = isset($data['student_name']) ? strtoupper($data['student_name']) : (isset($data['student_code']) ? "STUDENT CODE: " . $data['student_code'] : 'UNKNOWN STUDENT'); | |
| $this->centerText($image, 14, $boxY + 26, $studentName, $grey); | |
| // Table Header | |
| $tableY = 300; | |
| // 6 Columns: Description, Term/Session, Amount Billed, Amount Paid, Paid To Date, To Balance | |
| // Width 900. Margins 40. Active 820. | |
| // Approx X: 40, 320, 440, 560, 680, 800 (Right Boundary for last col is width-40 = 860) | |
| // Let's adjust spacing. | |
| $cols = [40, 300, 440, 550, 660, 780]; | |
| imageline($image, 40, $tableY, $width - 40, $tableY, $black); | |
| imageline($image, 40, $tableY + 30, $width - 40, $tableY + 30, $black); | |
| if ($this->fontPath) { | |
| imagettftext($image, 8, 0, $cols[0], $tableY + 20, $black, $this->fontPath, "FEE DESCRIPTION"); | |
| imagettftext($image, 8, 0, $cols[1], $tableY + 20, $black, $this->fontPath, "TERM/SESSION"); | |
| imagettftext($image, 8, 0, $cols[2], $tableY + 20, $black, $this->fontPath, "AMOUNT BILLED"); | |
| imagettftext($image, 8, 0, $cols[3], $tableY + 20, $black, $this->fontPath, "AMOUNT PAID"); | |
| imagettftext($image, 8, 0, $cols[4], $tableY + 20, $black, $this->fontPath, "PAID TO DATE"); | |
| imagettftext($image, 8, 0, $cols[5], $tableY + 20, $black, $this->fontPath, "TO BALANCE"); | |
| } else { | |
| // Fallback font | |
| imagestring($image, 3, $cols[0], $tableY + 5, "FEE DESCRIPTION", $black); | |
| imagestring($image, 3, $cols[1], $tableY + 5, "TERM/SESSION", $black); | |
| imagestring($image, 3, $cols[2], $tableY + 5, "BILLED", $black); | |
| imagestring($image, 3, $cols[3], $tableY + 5, "PAID", $black); | |
| imagestring($image, 3, $cols[4], $tableY + 5, "PAID TO DATE", $black); | |
| imagestring($image, 3, $cols[5], $tableY + 5, "TO BALANCE", $black); | |
| } | |
| // Rows | |
| $currY = $tableY + 30; | |
| $totalBilled = 0; | |
| $totalPaidToDate = 0; | |
| $totalBalance = 0; | |
| // Total Paid is passed from outside | |
| $totalAmountPaid = 0; | |
| if (isset($data['allocations']) && is_array($data['allocations'])) { | |
| foreach ($data['allocations'] as $alloc) { | |
| $desc = $alloc['description']; | |
| $session = $alloc['academic_session'] ?? ''; | |
| $term = $alloc['term_of_session'] ?? ''; | |
| // Format: Term/Session e.g. "1/2025" or "1st/2025" | |
| // Assuming simpler "1/2025" format for space | |
| $termSessionStr = "$term/$session"; | |
| $billedVal = $alloc['amount_billed'] ?? 0; | |
| $paidVal = $alloc['amount'] ?? 0; | |
| $paidToDateVal = $alloc['total_paid_to_date'] ?? 0; | |
| $balanceVal = $alloc['balance'] ?? 0; | |
| $totalBilled += $billedVal; | |
| $totalAmountPaid += $paidVal; | |
| $totalPaidToDate += $paidToDateVal; | |
| $totalBalance += $balanceVal; | |
| $billed = number_format($billedVal, 2); | |
| $paid = number_format($paidVal, 2); | |
| $paidToDate = number_format($paidToDateVal, 2); | |
| $balance = number_format($balanceVal, 2); | |
| $currY += 30; | |
| if ($this->fontPath) { | |
| imagettftext($image, 8, 0, $cols[0], $currY, $black, $this->fontPath, substr($desc, 0, 35)); | |
| imagettftext($image, 8, 0, $cols[1], $currY, $black, $this->fontPath, $termSessionStr); // Term/Session | |
| } else { | |
| imagestring($image, 3, $cols[0], $currY - 10, substr($desc, 0, 35), $black); | |
| imagestring($image, 3, $cols[1], $currY - 10, $termSessionStr, $black); | |
| } | |
| // Right Alignment using approx right boundaries | |
| // Col 2 (Billed): starts 440. Right align around 530? | |
| // Col 3 (Paid): starts 550. | |
| // Col 4 (PTD): starts 660. | |
| // Col 5 (Bal): starts 780. | |
| $rbBilled = 530; | |
| $rbPaid = 640; | |
| $rbPTD = 760; | |
| $rbBal = $width - 40; | |
| $this->rightAlignText($image, 8, $rbBilled, $currY, $billed, $black); | |
| $this->rightAlignText($image, 8, $rbPaid, $currY, $paid, $black); | |
| $this->rightAlignText($image, 8, $rbPTD, $currY, $paidToDate, $black); | |
| $this->rightAlignText($image, 8, $rbBal, $currY, $balance, $black); | |
| } | |
| } | |
| // Total Row | |
| $currY += 20; | |
| imageline($image, 40, $currY, $width - 40, $currY, $black); | |
| $currY += 26; | |
| $totalPaidStr = number_format($data['total_paid'] ?? 0, 2); | |
| $totalBilledStr = number_format($totalBilled, 2); | |
| $totalPaidToDateStr = number_format($totalPaidToDate, 2); | |
| $totalBalanceStr = number_format($totalBalance, 2); | |
| // Columns for totals | |
| $rbBilled = 530; | |
| $rbPaid = 640; | |
| $rbPTD = 760; | |
| $rbBal = $width - 40; | |
| $this->rightAlignText($image, 9, $rbBilled, $currY, $totalBilledStr, $grey); | |
| $this->rightAlignText($image, 9, $rbPaid, $currY, $totalPaidStr, $grey); | |
| $this->rightAlignText($image, 9, $rbPTD, $currY, $totalPaidToDateStr, $grey); | |
| $this->rightAlignText($image, 9, $rbBal, $currY, $totalBalanceStr, $grey); | |
| imageline($image, 40, $currY + 14, $width - 40, $currY + 14, $black); | |
| // Footer | |
| $footerY = $currY + 50; | |
| if ($this->fontPath) { | |
| imagettftext($image, 9, 0, 40, $footerY, $black, $this->fontPath, "RECEIPT DULY VERIFIED BY"); | |
| imagettftext($image, 9, 0, 40, $footerY + 20, $black, $this->fontPath, "BURSAR'S OFFICE"); | |
| } else { | |
| imagestring($image, 3, 40, $footerY - 10, "RECEIPT DULY VERIFIED BY", $black); | |
| imagestring($image, 3, 40, $footerY + 10, "BURSAR'S OFFICE", $black); | |
| } | |
| $receivedY = $footerY; | |
| $this->rightAlignText($image, 9, $width - 40, $receivedY, "RECEIVED IN", $black); | |
| $this->rightAlignText($image, 9, $width - 40, $receivedY + 15, "BANK", $black); | |
| $this->rightAlignText($image, 9, $width - 40, $receivedY + 40, "RECEIVED ON", $black); | |
| $dateStr = isset($data['payment_date']) ? strtoupper(date("F d, Y", strtotime($data['payment_date']))) : date("F d, Y"); | |
| $this->rightAlignText($image, 9, $width - 40, $receivedY + 55, $dateStr, $black); | |
| ob_start(); | |
| imagepng($image); | |
| $content = ob_get_clean(); | |
| imagedestroy($image); | |
| return $content; | |
| } | |
| public function generateBase64($data) | |
| { | |
| $content = $this->generate($data); | |
| return base64_encode($content); | |
| } | |
| private function centerText($image, $size, $y, $text, $color) | |
| { | |
| if ($this->fontPath) { | |
| try { | |
| $box = imagettfbbox($size, 0, $this->fontPath, $text); | |
| $textW = $box[2] - $box[0]; | |
| $x = (int) ((imagesx($image) - $textW) / 2); | |
| imagettftext($image, $size, 0, $x, (int) $y, $color, $this->fontPath, $text); | |
| return; | |
| } catch (Exception $e) { | |
| // Fallthrough to built-in font | |
| } | |
| } | |
| // Fallback or if fontPath is missing | |
| $gdSize = (int) min(5, max(1, floor($size / 3))); | |
| $charWidth = imagefontwidth($gdSize); | |
| $textW = strlen($text) * $charWidth; | |
| $x = (int) ((imagesx($image) - $textW) / 2); | |
| imagestring($image, $gdSize, $x, (int) $y, $text, $color); | |
| } | |
| private function rightAlignText($image, $size, $rightX, $y, $text, $color) | |
| { | |
| if ($this->fontPath) { | |
| try { | |
| $box = imagettfbbox($size, 0, $this->fontPath, $text); | |
| $textW = $box[2] - $box[0]; | |
| $x = (int) ($rightX - $textW); | |
| imagettftext($image, $size, 0, $x, (int) $y, $color, $this->fontPath, $text); | |
| return; | |
| } catch (Exception $e) { | |
| // Fallthrough | |
| } | |
| } | |
| $gdSize = (int) min(5, max(1, floor($size / 3))); | |
| $charWidth = imagefontwidth($gdSize); | |
| $x = (int) ($rightX - (strlen($text) * $charWidth)); | |
| imagestring($image, $gdSize, $x, (int) $y, $text, $color); | |
| } | |
| } | |