reconstruction of the project
This commit is contained in:
@@ -0,0 +1,691 @@
|
||||
<?php
|
||||
|
||||
namespace App\Controllers\View;
|
||||
|
||||
use App\Controllers\BaseController;
|
||||
use CodeIgniter\HTTP\ResponseInterface;
|
||||
use App\Models\UserModel;
|
||||
use App\Models\LateSlipLogModel;
|
||||
use App\Models\ConfigurationModel;
|
||||
use Mike42\Escpos\PrintConnectors\WindowsPrintConnector;
|
||||
|
||||
|
||||
// ESC/POS
|
||||
use Mike42\Escpos\Printer;
|
||||
use Mike42\Escpos\EscposImage;
|
||||
use Mike42\Escpos\PrintConnectors\NetworkPrintConnector;
|
||||
use Mike42\Escpos\PrintConnectors\UsbPrintConnector;
|
||||
use Dompdf\Dompdf;
|
||||
use Dompdf\Options;
|
||||
|
||||
class SlipPrinterController extends BaseController
|
||||
{
|
||||
protected $configModel;
|
||||
protected $semester;
|
||||
protected $schoolYear;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
helper(['form', 'url']);
|
||||
$this->configModel = new ConfigurationModel();
|
||||
$this->semester = $this->configModel->getConfig('semester');
|
||||
$this->schoolYear = $this->configModel->getConfig('school_year');
|
||||
}
|
||||
/**
|
||||
* POST /slips/print
|
||||
* Expected fields (POST): school_year, student_name, date, time_in, grade, reason, admin_name
|
||||
* You can wire this to your own form/data source; all fields are strings.
|
||||
*/
|
||||
public function print(): ResponseInterface
|
||||
{
|
||||
$req = $this->request;
|
||||
|
||||
$data = [
|
||||
'school_year' => trim((string) $req->getVar('school_year') ?: ''),
|
||||
'student_name' => trim((string) $req->getVar('student_name') ?: ''),
|
||||
'date' => trim((string) $req->getVar('date') ?: date('m/d/Y')),
|
||||
'time_in' => trim((string) $req->getVar('time_in') ?: date('h:i A')),
|
||||
'grade' => trim((string) $req->getVar('grade') ?: ''),
|
||||
'reason' => trim((string) $req->getVar('reason') ?: ''),
|
||||
'admin_name' => trim((string) $req->getVar('admin_name') ?: ''),
|
||||
];
|
||||
|
||||
// Always prefer current logged-in user's full name from DB
|
||||
$adminFromDb = $this->currentAdminName();
|
||||
if ($adminFromDb !== '') {
|
||||
$data['admin_name'] = $adminFromDb;
|
||||
}
|
||||
|
||||
// Validate minimal fields
|
||||
if ($data['student_name'] === '') {
|
||||
return $this->response->setStatusCode(422)->setJSON(['error' => 'Student name is required.']);
|
||||
}
|
||||
|
||||
try {
|
||||
// Persist a log (best-effort) before generating PDF
|
||||
try {
|
||||
$printedBy = (int) (session()->get('user_id') ?? 0) ?: null;
|
||||
$cfg = new ConfigurationModel();
|
||||
$semester = (string)($cfg->getConfig('semester') ?? '');
|
||||
$logData = [
|
||||
'school_year' => $data['school_year'],
|
||||
'semester' => $semester,
|
||||
'student_name' => $data['student_name'],
|
||||
'slip_date' => $this->toDbDate($data['date']),
|
||||
'time_in' => $this->toDbTime($data['time_in']),
|
||||
'grade' => $data['grade'],
|
||||
'reason' => $data['reason'],
|
||||
'admin_name' => $data['admin_name'],
|
||||
];
|
||||
(new LateSlipLogModel())->logSlip($logData, $printedBy);
|
||||
} catch (\Throwable $e) {
|
||||
log_message('error', 'Late slip log insert failed: ' . $e->getMessage());
|
||||
}
|
||||
|
||||
// Generate and stream a PDF instead of direct printer output
|
||||
return $this->renderSlipPdfResponse($data);
|
||||
} catch (\Throwable $e) {
|
||||
log_message('error', 'Late slip PDF generation failed: ' . $e->getMessage());
|
||||
return $this->response->setStatusCode(500)->setJSON([
|
||||
'error' => 'Printing failed.',
|
||||
'details' => $e->getMessage(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Show or print preview of late slips.
|
||||
*/
|
||||
public function preview(): ResponseInterface
|
||||
{
|
||||
$req = $this->request;
|
||||
|
||||
try {
|
||||
// ---- Step 1: Resolve current school year ----
|
||||
$currentYear = trim((string) ($this->schoolYear ?? ''));
|
||||
if ($currentYear === '') {
|
||||
$latest = (new LateSlipLogModel())
|
||||
->select('school_year')
|
||||
->orderBy('id', 'DESC')
|
||||
->first();
|
||||
$currentYear = $latest['school_year'] ?? (date('Y') . '-' . (date('Y') + 1));
|
||||
}
|
||||
|
||||
$selectedYear = trim((string) $req->getVar('school_year') ?: $currentYear);
|
||||
$selectedSemester = strtolower(trim((string) $req->getVar('semester') ?: ''));
|
||||
|
||||
$data = [
|
||||
'school_year' => $selectedYear,
|
||||
'student_name' => trim((string) $req->getVar('student_name') ?: ''),
|
||||
'date' => trim((string) $req->getVar('date') ?: date('m/d/Y')),
|
||||
'time_in' => trim((string) $req->getVar('time_in') ?: date('h:i A')),
|
||||
'grade' => trim((string) $req->getVar('grade') ?: ''),
|
||||
'reason' => trim((string) $req->getVar('reason') ?: ''),
|
||||
'admin_name' => trim((string) $req->getVar('admin_name') ?: ''),
|
||||
];
|
||||
|
||||
// Prefer logged-in admin name
|
||||
$adminFromDb = $this->currentAdminName();
|
||||
if ($adminFromDb !== '') {
|
||||
$data['admin_name'] = $adminFromDb;
|
||||
}
|
||||
|
||||
// ---- Handle GET (HTML preview) ----
|
||||
if (strtolower($req->getMethod()) === 'get') {
|
||||
$rows = [];
|
||||
$logModel = new LateSlipLogModel();
|
||||
$builder = $logModel;
|
||||
|
||||
// Filter by school year if not empty
|
||||
if ($selectedYear !== '') {
|
||||
$builder->where('school_year', $selectedYear);
|
||||
}
|
||||
|
||||
// Normalize and apply semester filter
|
||||
$hasFilter = false;
|
||||
if ($selectedSemester !== '') {
|
||||
$hasFilter = true;
|
||||
if ($selectedSemester === 'fall') {
|
||||
$builder->groupStart()
|
||||
->where('semester', 'fall')
|
||||
->orWhere('semester', '1')
|
||||
->orWhere('semester', 1)
|
||||
->groupEnd();
|
||||
} elseif ($selectedSemester === 'spring') {
|
||||
$builder->groupStart()
|
||||
->where('semester', 'spring')
|
||||
->orWhere('semester', '2')
|
||||
->orWhere('semester', 2)
|
||||
->groupEnd();
|
||||
}
|
||||
}
|
||||
|
||||
// ---- Execute query ----
|
||||
$logs = $builder->orderBy('id', 'DESC')->findAll(50);
|
||||
|
||||
// 🟢 Only use fallback when *no filters are selected*
|
||||
if (empty($logs) && !$hasFilter && $selectedYear === '') {
|
||||
$logs = (new LateSlipLogModel())->orderBy('id', 'DESC')->findAll(50);
|
||||
}
|
||||
|
||||
// ---- Format rows ----
|
||||
foreach ($logs as $r) {
|
||||
$dispDate = '';
|
||||
$slipDateRaw = trim((string)($r['slip_date'] ?? ''));
|
||||
if ($slipDateRaw !== '' && $slipDateRaw !== '0000-00-00') {
|
||||
$ts = strtotime($slipDateRaw);
|
||||
$dispDate = $ts ? date('m/d/Y', $ts) : '';
|
||||
}
|
||||
if ($dispDate === '') {
|
||||
$printedAt = trim((string)($r['printed_at'] ?? ''));
|
||||
if ($printedAt !== '') {
|
||||
try {
|
||||
$dispDate = local_date($printedAt, 'm/d/Y');
|
||||
} catch (\Throwable $e) {
|
||||
$ts = strtotime($printedAt);
|
||||
$dispDate = $ts ? date('m/d/Y', $ts) : '';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$dispTime = '';
|
||||
if (!empty($r['time_in'])) {
|
||||
$t = strtotime($r['time_in']) ?: strtotime('today ' . $r['time_in']);
|
||||
$dispTime = $t ? date('h:i A', $t) : '';
|
||||
}
|
||||
|
||||
$rows[] = [
|
||||
'school_year' => (string)($r['school_year'] ?? ''),
|
||||
'semester' => (string)($r['semester'] ?? ''),
|
||||
'student_name' => (string)($r['student_name'] ?? ''),
|
||||
'date' => $dispDate,
|
||||
'time_in' => $dispTime,
|
||||
'grade' => (string)($r['grade'] ?? ''),
|
||||
'reason' => (string)($r['reason'] ?? ''),
|
||||
'admin_name' => (string)($r['admin_name'] ?? ''),
|
||||
];
|
||||
}
|
||||
|
||||
// ---- Render the view ----
|
||||
$html = view('slips/preview_list', [
|
||||
'rows' => $rows,
|
||||
'school_year' => $selectedYear,
|
||||
'semester' => $selectedSemester,
|
||||
]);
|
||||
|
||||
return $this->response
|
||||
->setStatusCode(200)
|
||||
->setHeader('Content-Type', 'text/html; charset=UTF-8')
|
||||
->setBody($html);
|
||||
}
|
||||
|
||||
// ---- POST mode (JSON preview for printer) ----
|
||||
$cfg = $this->printerConfig();
|
||||
$lineWidth = (int) ($cfg['chars_per_line'] ?? 48);
|
||||
$feedLines = (int) ($cfg['feed_lines'] ?? 3);
|
||||
|
||||
$header = "Al Rahma School at ISGL\nSchool Year: {$data['school_year']}\n\n";
|
||||
$body = implode("\n", $this->buildSlipLines($data, $lineWidth));
|
||||
$footer = str_repeat("\n", $feedLines);
|
||||
$full = $header . $body . $footer;
|
||||
|
||||
return $this->response->setJSON([
|
||||
'ok' => true,
|
||||
'text' => $full,
|
||||
'width' => $lineWidth,
|
||||
]);
|
||||
} catch (\Throwable $e) {
|
||||
log_message('error', 'Late slip preview failed: ' . $e->getMessage());
|
||||
return $this->response->setStatusCode(500)->setJSON([
|
||||
'ok' => false,
|
||||
'error' => 'Preview failed.',
|
||||
'details' => $e->getMessage(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* POST /administrator/late_slips/reprint/{id}
|
||||
* Reprint a late slip from a saved log row, then redirect back with status.
|
||||
*/
|
||||
public function reprint($id = null): ResponseInterface
|
||||
{
|
||||
$id = (int) ($id ?? 0);
|
||||
if ($id <= 0) {
|
||||
return redirect()->back()->with('error', 'Invalid slip id.');
|
||||
}
|
||||
|
||||
$row = null;
|
||||
try {
|
||||
$row = (new LateSlipLogModel())->find($id);
|
||||
} catch (\Throwable $e) {
|
||||
$row = null;
|
||||
}
|
||||
if (!$row) {
|
||||
return redirect()->back()->with('error', 'Slip not found.');
|
||||
}
|
||||
|
||||
// Map DB row to print payload
|
||||
$dateDisplay = '';
|
||||
if (!empty($row['slip_date'])) {
|
||||
$ts = strtotime($row['slip_date']);
|
||||
$dateDisplay = $ts ? date('m/d/Y', $ts) : '';
|
||||
}
|
||||
if ($dateDisplay === '') $dateDisplay = date('m/d/Y');
|
||||
|
||||
$timeDisplay = '';
|
||||
if (!empty($row['time_in'])) {
|
||||
$ts = strtotime($row['time_in']);
|
||||
if ($ts === false) {
|
||||
// Try combining with today
|
||||
$ts = strtotime('today ' . $row['time_in']);
|
||||
}
|
||||
$timeDisplay = $ts ? date('h:i A', $ts) : '';
|
||||
}
|
||||
if ($timeDisplay === '') $timeDisplay = date('h:i A');
|
||||
|
||||
$data = [
|
||||
'school_year' => (string) ($row['school_year'] ?? ''),
|
||||
'student_name' => (string) ($row['student_name'] ?? ''),
|
||||
'date' => $dateDisplay,
|
||||
'time_in' => $timeDisplay,
|
||||
'grade' => (string) ($row['grade'] ?? ''),
|
||||
'reason' => (string) ($row['reason'] ?? ''),
|
||||
'admin_name' => (string) ($row['admin_name'] ?? ''),
|
||||
];
|
||||
|
||||
// Always prefer current logged-in user's full name from DB
|
||||
$adminFromDb = $this->currentAdminName();
|
||||
if ($adminFromDb !== '') {
|
||||
$data['admin_name'] = $adminFromDb;
|
||||
}
|
||||
|
||||
try {
|
||||
// Format lines and print
|
||||
$lines = $this->buildSlipLines($data);
|
||||
$printer = $this->makePrinter();
|
||||
|
||||
$printer->setJustification(Printer::JUSTIFY_CENTER);
|
||||
$printer->setEmphasis(true);
|
||||
$printer->text("Al Rahma School at ISGL\n");
|
||||
$printer->setEmphasis(false);
|
||||
$printer->text("School Year: {$data['school_year']}\n");
|
||||
$printer->feed();
|
||||
|
||||
$printer->setJustification(Printer::JUSTIFY_LEFT);
|
||||
foreach ($lines as $ln) {
|
||||
$printer->text($ln . "\n");
|
||||
}
|
||||
|
||||
$printer->feed((int) ($this->printerConfig()['feed_lines'] ?? 3));
|
||||
$printer->cut();
|
||||
$printer->close();
|
||||
|
||||
// Log again (best-effort) for audit trail
|
||||
try {
|
||||
$printedBy = (int) (session()->get('user_id') ?? 0) ?: null;
|
||||
$cfg = new ConfigurationModel();
|
||||
$semester = (string)($cfg->getConfig('semester') ?? '');
|
||||
$logData = [
|
||||
'school_year' => $data['school_year'],
|
||||
'semester' => $semester,
|
||||
'student_name' => $data['student_name'],
|
||||
'slip_date' => $this->toDbDate($data['date']),
|
||||
'time_in' => $this->toDbTime($data['time_in']),
|
||||
'grade' => $data['grade'],
|
||||
'reason' => $data['reason'],
|
||||
'admin_name' => $data['admin_name'],
|
||||
];
|
||||
(new LateSlipLogModel())->logSlip($logData, $printedBy);
|
||||
} catch (\Throwable $e) {
|
||||
log_message('error', 'Late slip reprint log insert failed: ' . $e->getMessage());
|
||||
}
|
||||
|
||||
return redirect()->back()->with('success', 'Late slip sent to printer.');
|
||||
} catch (\Throwable $e) {
|
||||
return redirect()->back()->with('error', 'Reprint failed: ' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Build monospaced slip lines for an 80mm (typically 48 chars) receipt.
|
||||
*
|
||||
* Layout required:
|
||||
* Al Rahma School at ISGL "SchoolYear"
|
||||
* Student Name ___________________________
|
||||
* Date ______________ Time In ______________
|
||||
* Grade _________________________________
|
||||
* Reason _________________________________
|
||||
* _______________________________________
|
||||
* Admin Name ____________________________
|
||||
*/
|
||||
private function buildSlipLines(array $data, ?int $lineWidth = null): array
|
||||
{
|
||||
// Choose a sane default for 58mm rolls (~24–32 chars). Fall back to env config.
|
||||
$cfgWidth = (int) ($this->printerConfig()['chars_per_line'] ?? 0);
|
||||
$w = $lineWidth ?? ($cfgWidth > 0 ? $cfgWidth : 32);
|
||||
|
||||
$lines = [];
|
||||
$lines[] = $this->fitLine('Student Name: ' . (string)($data['student_name'] ?? ''), $w);
|
||||
$lines[] = $this->fitLine('Date: ' . (string)($data['date'] ?? '') . ' ' . 'Time In: ' . (string)($data['time_in'] ?? ''), $w);
|
||||
$lines[] = $this->fitLine('Grade: ' . (string)($data['grade'] ?? ''), $w);
|
||||
$lines[] = $this->fitLine('Reason: ' . (string)($data['reason'] ?? ''), $w);
|
||||
$lines[] = $this->fitLine('Admin: ' . (string)($data['admin_name'] ?? ''), $w);
|
||||
|
||||
return $lines;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create and return a configured Mike42\Escpos\Printer instance.
|
||||
*/
|
||||
private function makePrinter(): Printer
|
||||
{
|
||||
$cfg = $this->printerConfig();
|
||||
$mode = strtolower((string) ($cfg['mode'] ?? 'network'));
|
||||
|
||||
if ($mode === 'windows') {
|
||||
if (stripos(PHP_OS_FAMILY, 'Windows') !== false) {
|
||||
$name = (string) ($cfg['windows_name'] ?? 'POS-80');
|
||||
$connector = new \Mike42\Escpos\PrintConnectors\WindowsPrintConnector($name);
|
||||
return new Printer($connector);
|
||||
}
|
||||
log_message('warning', 'PRINTER_MODE=windows on non-Windows OS; falling back to network.');
|
||||
$mode = 'network';
|
||||
}
|
||||
|
||||
if ($mode === 'usb') {
|
||||
if (stripos(PHP_OS_FAMILY, 'Windows') !== false) {
|
||||
$name = (string) ($cfg['windows_name'] ?? 'POS-80');
|
||||
$connector = new \Mike42\Escpos\PrintConnectors\WindowsPrintConnector($name);
|
||||
return new Printer($connector);
|
||||
}
|
||||
$vidStr = (string) ($cfg['usb_vid'] ?? '');
|
||||
$pidStr = (string) ($cfg['usb_pid'] ?? '');
|
||||
if ($vidStr === '' || $pidStr === '') {
|
||||
throw new \RuntimeException('USB mode requires PRINTER_USB_VID and PRINTER_USB_PID');
|
||||
}
|
||||
$vid = $this->hexToInt($vidStr);
|
||||
$pid = $this->hexToInt($pidStr);
|
||||
$connector = new \Mike42\Escpos\PrintConnectors\UsbPrintConnector($vid, $pid);
|
||||
return new Printer($connector);
|
||||
}
|
||||
|
||||
// Default: network
|
||||
$host = (string) ($cfg['host'] ?? '192.168.1.100');
|
||||
$port = (int) ($cfg['port'] ?? 9100);
|
||||
$connector = new \Mike42\Escpos\PrintConnectors\NetworkPrintConnector($host, $port);
|
||||
return new Printer($connector);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Build a "Label: ________VALUE" style line, using underscores to fill remaining space.
|
||||
* If $value is empty, just render a long blank line after the label.
|
||||
*/
|
||||
private function labelWithLine(string $label, string $value, int $width, ?int $fillCount = null): string
|
||||
{
|
||||
$label = rtrim($label, ':') . ': ';
|
||||
$maxFill = $fillCount ?? max(0, $width - strlen($label));
|
||||
$filled = $value !== ''
|
||||
? $this->fixedField($value, $maxFill, '_')
|
||||
: str_repeat('_', $maxFill);
|
||||
|
||||
$line = $label . $filled;
|
||||
|
||||
// Ensure total width, pad or trim
|
||||
return $this->fitLine($line, $width);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a single field like "Date: ________" (or with value).
|
||||
*/
|
||||
private function fieldWithBlanks(string $label, string $value, int $fieldWidth): string
|
||||
{
|
||||
$label = rtrim($label, ':') . ': ';
|
||||
$fill = max(0, $fieldWidth - strlen($label));
|
||||
$content = $value !== '' ? $this->fixedField($value, $fill, '_') : str_repeat('_', $fill);
|
||||
return $this->fitLine($label . $content, $fieldWidth);
|
||||
}
|
||||
|
||||
/**
|
||||
* Join two fixed-width fields into one line (e.g., Date field + Time In field).
|
||||
*/
|
||||
private function joinTwoFields(string $left, string $right, int $totalWidth): string
|
||||
{
|
||||
$gap = ' ';
|
||||
$line = $left . $gap . $right;
|
||||
return $this->fitLine($line, $totalWidth);
|
||||
}
|
||||
|
||||
/**
|
||||
* Pad/truncate a value to an exact width using a fill character (for underlines).
|
||||
*/
|
||||
private function fixedField(string $val, int $width, string $fillChar = '_'): string
|
||||
{
|
||||
$val = (string) $val;
|
||||
if (strlen($val) > $width) {
|
||||
return substr($val, 0, $width);
|
||||
}
|
||||
return $val . str_repeat($fillChar, max(0, $width - strlen($val)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure any line is exactly $width characters (trim or pad spaces).
|
||||
*/
|
||||
private function fitLine(string $line, int $width): string
|
||||
{
|
||||
$line = (string) $line;
|
||||
if (strlen($line) > $width) {
|
||||
return substr($line, 0, $width);
|
||||
}
|
||||
return $line . str_repeat(' ', $width - strlen($line));
|
||||
}
|
||||
|
||||
/**
|
||||
* "0x1a86" -> 0x1a86 (int). Accepts plain decimal too.
|
||||
*/
|
||||
private function hexToInt(string $hexOrDec): int
|
||||
{
|
||||
$hexOrDec = trim($hexOrDec);
|
||||
if ($hexOrDec === '') return 0;
|
||||
if (stripos($hexOrDec, '0x') === 0) {
|
||||
return intval($hexOrDec, 16);
|
||||
}
|
||||
return (int) $hexOrDec;
|
||||
}
|
||||
|
||||
/**
|
||||
* Consolidated printer configuration. Supports both dotted keys (printer.*)
|
||||
* and uppercase PRINTER_* keys from .env.
|
||||
*/
|
||||
private function printerConfig(): array
|
||||
{
|
||||
$get = function ($keys, $default = null) {
|
||||
$keys = is_array($keys) ? $keys : [$keys];
|
||||
foreach ($keys as $k) {
|
||||
$v = env($k);
|
||||
if ($v !== null && $v !== '') return $v;
|
||||
}
|
||||
return $default;
|
||||
};
|
||||
|
||||
return [
|
||||
'mode' => strtolower((string) $get(['printer.mode', 'PRINTER_MODE'], 'network')),
|
||||
'host' => (string) $get(['printer.host', 'PRINTER_HOST'], '192.168.1.100'),
|
||||
'port' => (int) $get(['printer.port', 'PRINTER_PORT'], 9100),
|
||||
'usb_vid' => (string) $get(['printer.usb.vid', 'PRINTER_USB_VID'], ''),
|
||||
'usb_pid' => (string) $get(['printer.usb.pid', 'PRINTER_USB_PID'], ''),
|
||||
'windows_name' => (string) $get(['printer.windows.name', 'PRINTER_WINDOWS_NAME'], 'POS-80'),
|
||||
'chars_per_line' => (int) $get(['printer.chars_per_line', 'PRINTER_CHARS_PER_LINE'], 48),
|
||||
'feed_lines' => (int) $get(['printer.feed_lines', 'PRINTER_FEED_LINES'], 3),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert user-provided date/time strings into DB formats.
|
||||
*/
|
||||
private function toDbDate(?string $s): ?string
|
||||
{
|
||||
$s = trim((string)$s);
|
||||
if ($s === '') return null;
|
||||
// Explicitly parse common US formats to avoid day/month swaps.
|
||||
if (preg_match('/^\\d{4}-\\d{2}-\\d{2}$/', $s)) {
|
||||
return $s;
|
||||
}
|
||||
if (preg_match('/^\\d{1,2}\\/\\d{1,2}\\/\\d{4}$/', $s)) {
|
||||
$dt = \DateTime::createFromFormat('!m/d/Y', $s);
|
||||
return $dt ? $dt->format('Y-m-d') : null;
|
||||
}
|
||||
if (preg_match('/^\\d{1,2}-\\d{1,2}-\\d{4}$/', $s)) {
|
||||
$dt = \DateTime::createFromFormat('!m-d-Y', $s);
|
||||
return $dt ? $dt->format('Y-m-d') : null;
|
||||
}
|
||||
$ts = strtotime($s);
|
||||
return $ts ? date('Y-m-d', $ts) : null;
|
||||
}
|
||||
|
||||
private function toDbTime(?string $s): ?string
|
||||
{
|
||||
$s = trim((string)$s);
|
||||
if ($s === '') return null;
|
||||
$ts = strtotime($s);
|
||||
return $ts ? date('H:i:s', $ts) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute current admin full name using session user_id and users table.
|
||||
* Falls back to session firstname/lastname, then user_name, else ''.
|
||||
*/
|
||||
private function currentAdminName(): string
|
||||
{
|
||||
try {
|
||||
$uid = (int) (session()->get('user_id') ?? 0);
|
||||
if ($uid > 0) {
|
||||
$user = (new UserModel())
|
||||
->select('firstname, lastname')
|
||||
->find($uid);
|
||||
if ($user) {
|
||||
$full = trim((string)($user['firstname'] ?? '') . ' ' . (string)($user['lastname'] ?? ''));
|
||||
if ($full !== '') return $full;
|
||||
}
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
// ignore and fall back
|
||||
}
|
||||
|
||||
$first = trim((string)(session()->get('firstname') ?? ''));
|
||||
$last = trim((string)(session()->get('lastname') ?? ''));
|
||||
$full = trim($first . ' ' . $last);
|
||||
if ($full !== '') return $full;
|
||||
return (string)(session()->get('user_name') ?? '');
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the slip as a PDF and stream it inline to the browser.
|
||||
*/
|
||||
private function renderSlipPdfResponse(array $data): ResponseInterface
|
||||
{
|
||||
// Build HTML from a dedicated view
|
||||
// Determine paper size (mm)
|
||||
$paper = strtolower((string) ($this->request->getVar('paper') ?: env('SLIP_PAPER', 'card')));
|
||||
// Estimate dynamic height based on the PDF view font and line-height
|
||||
// Title + seven content lines = 8 rows total
|
||||
// Defaults match the current view: 13pt font, 2.0 line-height
|
||||
$fontPt = (float) ($this->request->getVar('font_pt') ?? env('SLIP_FONT_PT', 13.0));
|
||||
$lineH = (float) ($this->request->getVar('line_h') ?? env('SLIP_LINE_H', 2.0));
|
||||
$linesCount = (int) ($this->request->getVar('rows') ?? 8);
|
||||
if ($linesCount < 1) {
|
||||
$linesCount = 8;
|
||||
}
|
||||
$ptPerLine = $fontPt * $lineH;
|
||||
$mmPerPt = 25.4 / 72.0; // 1pt in mm
|
||||
$contentMm = $ptPerLine * $linesCount * $mmPerPt; // content only
|
||||
$pageMarginsMm = 3.0 * 2; // top+bottom from CSS
|
||||
// Allow caller to increase safety buffer if their viewer/driver adds spacing
|
||||
// Slightly bigger safety buffer to avoid second-page spillovers from drivers
|
||||
$fudgeMm = (float) ($this->request->getVar('fudge_mm') ?? env('SLIP_FUDGE_MM', 12.0));
|
||||
$dynHeightMm = $contentMm + $pageMarginsMm + $fudgeMm;
|
||||
|
||||
switch ($paper) {
|
||||
case 'cardp':
|
||||
case 'card-portrait':
|
||||
$wMm = 53.98;
|
||||
$hMm = max($dynHeightMm, 40.0); // portrait width, dynamic height
|
||||
$wChars = 30;
|
||||
break;
|
||||
case 'receipt80':
|
||||
$wMm = 72.0;
|
||||
$hMm = 90.0; // 80mm roll (printable ~72mm) — taller to fit larger font
|
||||
$wChars = 30;
|
||||
break;
|
||||
case 'receipt58':
|
||||
$wMm = 58.0;
|
||||
$hMm = 80.0; // 58mm roll — taller to fit larger font
|
||||
$wChars = 24;
|
||||
break;
|
||||
case 'card':
|
||||
default:
|
||||
$wMm = 85.60;
|
||||
$hMm = max($dynHeightMm, 40.0); // credit card width, dynamic height
|
||||
$wChars = 40;
|
||||
}
|
||||
|
||||
// Optional manual override via query: height_mm (or h)
|
||||
$hOverride = $this->request->getVar('height_mm') ?? $this->request->getVar('h');
|
||||
if (is_numeric($hOverride)) {
|
||||
$hMm = max(30.0, min(200.0, (float) $hOverride));
|
||||
}
|
||||
|
||||
// Build exactly six lines in monospace formatting
|
||||
$w = $wChars; // characters per line for layout
|
||||
$lines = [];
|
||||
$lines[] = $this->fitLine('Al Rahma School at ISGL ' . ($data['school_year'] ?? ''), $w);
|
||||
// Helper to compose "Label: value_____" without truncating value first
|
||||
$compose = function (string $label, string $value, int $padUnderscore = 0) use ($w) {
|
||||
$prefix = rtrim($label, ':') . ': ';
|
||||
$line = $prefix . $value;
|
||||
$len = strlen($line);
|
||||
if ($padUnderscore > 0 && $len < $w) {
|
||||
$line .= str_repeat('_', min($padUnderscore, $w - $len));
|
||||
}
|
||||
return $this->fitLine($line, $w);
|
||||
};
|
||||
// Student Name (no trailing underscores)
|
||||
$lines[] = $compose('Student Name', (string)($data['student_name'] ?? ''), 0);
|
||||
// Date and Time In on separate lines
|
||||
$lines[] = $this->fitLine('Date: ' . (string)($data['date'] ?? ''), $w);
|
||||
$lines[] = $this->fitLine('Time In: ' . (string)($data['time_in'] ?? ''), $w);
|
||||
// Grade and Reason (no trailing underscores)
|
||||
$lines[] = $compose('Grade', (string)($data['grade'] ?? ''), 0);
|
||||
$lines[] = $compose('Reason', (string)($data['reason'] ?? ''), 0);
|
||||
// Admin: do not underline, just value
|
||||
$lines[] = $this->fitLine('Admin: ' . (string)($data['admin_name'] ?? ''), $w);
|
||||
|
||||
$html = view('slips/slip_pdf', ['entry' => $data, 'lines' => $lines, 'page' => ['w_mm' => $wMm, 'h_mm' => $hMm]]);
|
||||
|
||||
$options = new Options();
|
||||
$options->set('isRemoteEnabled', true);
|
||||
$options->set('defaultFont', 'Courier');
|
||||
|
||||
$dompdf = new Dompdf($options);
|
||||
$dompdf->loadHtml($html, 'UTF-8');
|
||||
// Ensure minimum height is enough for current content calculation
|
||||
if ($hMm < $dynHeightMm) {
|
||||
$hMm = $dynHeightMm;
|
||||
}
|
||||
|
||||
// Set paper size from selected mm values -> points
|
||||
$mmToPt = 72 / 25.4;
|
||||
$wPt = $wMm * $mmToPt;
|
||||
$hPt = $hMm * $mmToPt;
|
||||
$dompdf->setPaper([0, 0, $wPt, $hPt]);
|
||||
$dompdf->render();
|
||||
|
||||
$filename = 'LateSlip_' . preg_replace('/[^A-Za-z0-9]+/', '_', (string)($data['student_name'] ?? 'slip')) . '_' . date('Ymd_His') . '.pdf';
|
||||
|
||||
return $this->response
|
||||
->setHeader('Content-Type', 'application/pdf')
|
||||
->setHeader('Content-Disposition', 'inline; filename="' . $filename . '"')
|
||||
->setBody($dompdf->output());
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user