diff --git a/app/Controllers/Diagnosis.php b/app/Controllers/Diagnosis.php index 5917c34..46027a0 100644 --- a/app/Controllers/Diagnosis.php +++ b/app/Controllers/Diagnosis.php @@ -5,6 +5,8 @@ namespace App\Controllers; use App\Controllers\BaseController; use CodeIgniter\I18n\Time; use Exception; +use Mpdf\HTMLParserMode; +use Mpdf\Mpdf; class Diagnosis extends BaseController { @@ -341,6 +343,7 @@ class Diagnosis extends BaseController 'lab_type' => $labType, 'lab_type_name' => $this->getLabTypeName($labType), 'lab_type_route' => $this->getLabTypeRoute($labType), + 'print_templates' => $this->getDiagnosisPrintTemplates($labType), ]; return view('diagnosis/pathology_detail', $data); @@ -421,6 +424,7 @@ class Diagnosis extends BaseController 'invoiceId' => $invoiceId, 'labType' => $labType, 'lab_type' => $labType, + 'print_templates' => $this->getDiagnosisPrintTemplates($labType), ]; $view = view('diagnosis/lab_invoice_main_test_list', $data); @@ -1046,6 +1050,7 @@ class Diagnosis extends BaseController public function openReportEditorPage($labReqId) { $labReqId = (int) $labReqId; + $editReason = trim((string) ($this->request->getGet('edit_reason') ?? '')); $reportRow = $this->db->table('lab_request')->where('id', $labReqId)->get()->getRow(); if (! $reportRow) { @@ -1111,6 +1116,7 @@ class Diagnosis extends BaseController return view('diagnosis/lab_final_report_editor', [ 'report_format' => [$reportRow], 'radiology_ultrasound_template' => $templates, + 'edit_reason' => $editReason, ]); } @@ -1139,14 +1145,28 @@ class Diagnosis extends BaseController public function finalUpdateXray($labReqId) { $labReqId = (int) $labReqId; - $htmlData = (string) $this->request->getPost('HTMLData'); - $impression = (string) $this->request->getPost('report_data_Impression'); + $htmlData = (string) ($this->request->getPost('HTMLData') ?? $this->request->getPost('report_data') ?? ''); + $impression = (string) ($this->request->getPost('report_data_Impression') ?? $this->request->getPost('report_data_impression') ?? ''); + $editReason = trim((string) ($this->request->getPost('edit_reason') ?? '')); if ($labReqId <= 0) { return $this->response->setJSON(['status' => 'error', 'message' => 'Invalid request id']); } try { + $before = $this->db->table('lab_request') + ->select('id, status, report_edit_req_no, Report_Data, report_data_Impression') + ->where('id', $labReqId) + ->get(1) + ->getRowArray() ?? []; + + if ((int) ($before['status'] ?? 0) === 2 && $editReason === '') { + return $this->response->setJSON([ + 'status' => 'error', + 'message' => 'Edit reason is required for verified report changes (NABH audit).', + ]); + } + $this->db->table('lab_request') ->where('id', $labReqId) ->update([ @@ -1154,6 +1174,35 @@ class Diagnosis extends BaseController 'report_data_Impression' => $impression, ]); + $fields = $this->db->getFieldNames('lab_request'); + if (in_array('report_edit_req_no', $fields, true) && (int) ($before['status'] ?? 0) === 2) { + $nextEditNo = (int) ($before['report_edit_req_no'] ?? 0) + 1; + $this->db->table('lab_request') + ->where('id', $labReqId) + ->update(['report_edit_req_no' => $nextEditNo]); + } + + if ($this->db->tableExists('lab_log')) { + $user = service('auth')->user(); + $userId = (int) ($user->id ?? 0); + $userName = $user ? trim(($user->first_name ?? '') . ' ' . ($user->last_name ?? '')) : 'System'; + + $beforeHash = md5((string) ($before['Report_Data'] ?? '') . '|' . (string) ($before['report_data_Impression'] ?? '')); + $afterHash = md5($htmlData . '|' . $impression); + $reasonLog = $editReason !== '' ? $editReason : 'NA'; + + $this->db->table('lab_log')->insert([ + 'lab_repo_id' => $labReqId, + 'log_by_id' => $userId, + 'log_by' => $userName, + 'log_type_id' => 0, + 'log_type' => 'Report Edit', + 'log_Faults_id' => 0, + 'log_Faults' => 'Radiology', + 'comments' => 'Editor save [reason:' . substr($reasonLog, 0, 350) . '] [before:' . substr($beforeHash, 0, 8) . ' after:' . substr($afterHash, 0, 8) . ']', + ]); + } + return $this->response->setJSON(['status' => 'success', 'message' => 'Saved']); } catch (\Throwable $e) { return $this->response->setJSON(['status' => 'error', 'message' => $e->getMessage()]); @@ -1212,23 +1261,624 @@ class Diagnosis extends BaseController public function printSingleReport($labReqId) { $labReqId = (int) $labReqId; + $templateId = (int) ($this->request->getGet('template_id') ?? 0); + + set_time_limit(300); if ($labReqId <= 0) { return '
No report data available for this request. Please use Edit and save report content.
'; + } + + $genderValue = (int) ($row->gender ?? 0); + $genderText = $genderValue === 1 ? 'Male' : ($genderValue === 2 ? 'Female' : '-'); + + $ageText = '-'; + if (function_exists('get_age_1')) { + $ageText = (string) get_age_1( + $row->dob ?? null, + $row->age ?? '', + $row->age_in_month ?? '', + $row->estimate_dob ?? '' + ); + } elseif (! empty($row->age)) { + $ageText = (string) $row->age; + } + + $printSetting = $this->getDiagnosisTemplateSetting((int) ($row->lab_type ?? 0), $templateId); + $isPlainPaper = false; + + // Compute display values here so they are available for token substitution. + $patientName = trim((string) ($row->p_fname ?? '')); + $relativeName = trim((string) ($row->p_relative ?? '')); + $relativeText = trim((string) ($row->p_rname ?? '')); + $repoTitle = trim((string) ($row->report_name ?? $row->repo_title ?? $row->RepoGrp ?? 'Report')); + $headRow = $this->db->table('diagnosis_head_name') + ->where('d_type', (int) ($row->lab_type ?? 0)) + ->get(1) + ->getRow(); + + $tokens = $this->buildPdfTokens([ + 'invoice_code' => $row->invoice_code ?? '', + 'patient_name' => $patientName, + 'relative_type' => $relativeName, + 'relative_name' => $relativeText, + 'age' => $ageText, + 'gender' => $genderText, + 'uhid' => $row->p_code ?? '', + 'collected_time' => $row->collected_time ?? '', + 'reported_time' => $row->reported_time ?? '', + 'report_title' => $repoTitle, + 'doctor_name' => $headRow->doc_name ?? '', + 'doctor_education' => $headRow->doc_edu ?? '', + 'technician_name' => $headRow->tech_name ?? '', + 'signature_image_url' => (string) ($printSetting['signature_image'] ?? ''), ]); + + // Render the optional patient info template (tokens resolved). + $rawPatientInfoHtml = trim((string) ($printSetting['patient_info_html'] ?? '')); + if ($rawPatientInfoHtml !== '') { + $patientInfoHtml = $this->applyPdfTokens($rawPatientInfoHtml, $tokens); + } elseif (($printSetting['id'] ?? 0) <= 0) { + $patientInfoHtml = $this->buildDefaultPatientInfoHtml([ + 'invoice_code' => $row->invoice_code ?? '', + 'patient_name' => $patientName, + 'relative_name' => $relativeName, + 'relative_text' => $relativeText, + 'age_text' => $ageText, + 'gender_text' => $genderText, + 'uhid' => $row->p_code ?? '', + 'collected_time' => $row->collected_time ?? '', + 'reported_time' => $row->reported_time ?? '', + ]); + } else { + $patientInfoHtml = ''; + } + + $reportHtml = trim((string) ($printSetting['content_prefix_html'] ?? '')) . $reportHtml . trim((string) ($printSetting['content_suffix_html'] ?? '')); + + $pdfHtml = view('diagnosis/pdf_single_report', [ + 'report' => $row, + 'reportHtml' => $reportHtml, + 'isPlainPaper' => $isPlainPaper, + 'ageText' => $ageText, + 'genderText' => $genderText, + 'printSetting' => $printSetting, + 'patientName' => $patientName, + 'relativeName' => $relativeName, + 'relativeText' => $relativeText, + 'repoTitle' => $repoTitle, + 'patientInfoHtml' => $patientInfoHtml, + ]); + + $rawHeaderHtml = trim((string) ($printSetting['header_html'] ?? '')); + $rawFirstPageHeaderHtml = trim((string) ($printSetting['first_page_header_html'] ?? '')); + $rawFooterHtml = trim((string) ($printSetting['footer_html'] ?? '')); + $rawLastPageFooterHtml = trim((string) ($printSetting['last_page_footer_html'] ?? '')); + + $autoHeaderHtml = $this->buildAutoMpdfHeaderBlock( + $rawHeaderHtml, + $rawFirstPageHeaderHtml, + 'diag_auto_header_single' + ); + $autoFooterHtml = $this->buildAutoMpdfFooterBlock( + $rawFooterHtml, + $rawLastPageFooterHtml, + 'diag_auto_footer_single' + ); + + $prefixHtml = $this->applyPdfTokens(trim((string) ($printSetting['mpdf_prefix_html'] ?? '')) + . $autoHeaderHtml + . $autoFooterHtml, $tokens); + $suffixHtml = $this->applyPdfTokens(trim((string) ($printSetting['mpdf_suffix_html'] ?? '')), $tokens); + + $marginTop = $this->cmToMm($printSetting['page_margin_top_cm'] ?? 1.2, 1.2); + $marginBottom = $this->cmToMm($printSetting['page_margin_bottom_cm'] ?? 1.2, 1.2); + $marginLeft = $this->cmToMm($printSetting['page_margin_left_cm'] ?? 1.0, 1.0); + $marginRight = $this->cmToMm($printSetting['page_margin_right_cm'] ?? 1.0, 1.0); + $marginHeader = $this->cmToMm($printSetting['margin_header_cm'] ?? 0.5, 0.5); + $marginFooter = $this->cmToMm($printSetting['margin_footer_cm'] ?? 1.5, 1.5); + + $mpdfTempDir = WRITEPATH . 'cache' . DIRECTORY_SEPARATOR . 'mpdf'; + if (! is_dir($mpdfTempDir)) { + mkdir($mpdfTempDir, 0755, true); + } + + $mpdf = new Mpdf([ + 'format' => (string) ($printSetting['page_size'] ?? 'A4'), + 'margin_top' => $marginTop, + 'margin_bottom' => $marginBottom, + 'margin_left' => $marginLeft, + 'margin_right' => $marginRight, + 'margin_header' => $marginHeader, + 'margin_footer' => $marginFooter, + 'tempDir' => $mpdfTempDir, + 'autoScriptToLang' => false, + 'autoLanguageDetection' => false, + 'autoArabic' => false, + 'autoVietnamese' => false, + ]); + + // For plain HTML (without explicit mPDF tags), set header/footer via API. + $headerHasTags = preg_match('/<\s*(htmlpageheader|sethtmlpageheader)\b/i', $rawHeaderHtml . $rawFirstPageHeaderHtml) === 1; + $footerHasTags = preg_match('/<\s*(htmlpagefooter|sethtmlpagefooter)\b/i', $rawFooterHtml . $rawLastPageFooterHtml) === 1; + + if (! $headerHasTags) { + $plainHeader = $this->applyPdfTokens($rawHeaderHtml . $rawFirstPageHeaderHtml, $tokens); + if ($plainHeader !== '') { + $mpdf->SetHTMLHeader($plainHeader, 'O'); + $mpdf->SetHTMLHeader($plainHeader, 'E'); + } + } + + if (! $footerHasTags) { + $plainFooter = $this->applyPdfTokens($rawFooterHtml, $tokens); + if ($plainFooter === '') { + $plainFooter = $this->applyPdfTokens($rawLastPageFooterHtml, $tokens); + } + + if ($plainFooter !== '') { + $mpdf->SetHTMLFooter($plainFooter, 'O'); + $mpdf->SetHTMLFooter($plainFooter, 'E'); + } + } + + $backgroundImage = trim((string) ($printSetting['page_background_image'] ?? '')); + if ($backgroundImage !== '') { + $absolutePath = FCPATH . ltrim(str_replace('\\', '/', $backgroundImage), '/'); + if (is_file($absolutePath)) { + $mpdf->SetWatermarkImage($absolutePath, 1, [210, 297], 'F'); + $mpdf->showWatermarkImage = true; + } + } + + $watermarkType = (string) ($printSetting['watermark_type'] ?? 'none'); + $watermarkAlpha = (float) ($printSetting['watermark_alpha'] ?? 0.12); + if ($watermarkType === 'text') { + $text = trim((string) ($printSetting['watermark_text'] ?? '')); + if ($text !== '') { + $mpdf->SetWatermarkText($text, $watermarkAlpha); + $mpdf->showWatermarkText = true; + } + } elseif ($watermarkType === 'image') { + $wmImage = trim((string) ($printSetting['watermark_image'] ?? '')); + if ($wmImage !== '') { + $absolutePath = FCPATH . ltrim(str_replace('\\', '/', $wmImage), '/'); + if (is_file($absolutePath)) { + $mpdf->SetWatermarkImage($absolutePath, $watermarkAlpha); + $mpdf->showWatermarkImage = true; + } + } + } + + if ($prefixHtml !== '') { + $mpdf->WriteHTML($prefixHtml, HTMLParserMode::HTML_HEADER_BUFFER); + } + + $mpdf->WriteHTML($pdfHtml); + + if ($suffixHtml !== '') { + $mpdf->WriteHTML($suffixHtml, HTMLParserMode::HTML_HEADER_BUFFER, false, false); + } + + $invoiceCode = preg_replace('/[^A-Za-z0-9\-_]/', '_', (string) ($row->invoice_code ?? 'invoice')); + $fileName = 'Report_' . $invoiceCode . '_' . $labReqId . '.pdf'; + + return $this->response + ->setHeader('Content-Type', 'application/pdf') + ->setHeader('Cache-Control', 'no-store, no-cache, must-revalidate, max-age=0') + ->setHeader('Pragma', 'no-cache') + ->setHeader('Expires', '0') + ->setHeader('Content-Disposition', 'inline; filename="' . $fileName . '"') + ->setBody($mpdf->Output($fileName, 'S')); + } + + private function getDiagnosisPrintTemplates(int $labType): array + { + if ($labType <= 0 || ! $this->db->tableExists('diagnosis_print_templates')) { + return []; + } + + return $this->db->table('diagnosis_print_templates') + ->select('id, template_name, is_default') + ->where('modality', $labType) + ->where('status', 1) + ->orderBy('is_default', 'DESC') + ->orderBy('template_name', 'ASC') + ->get() + ->getResultArray(); + } + + private function getDiagnosisTemplateSetting(int $labType, int $templateId = 0): array + { + $legacy = $this->getDiagnosisPrintSetting($labType); + + $defaults = array_merge($legacy, [ + 'page_size' => 'A4', + 'page_margin_top_cm' => 1.2, + 'id' => 0, + 'page_margin_bottom_cm' => 1.2, + 'page_margin_left_cm' => 1.0, + 'page_margin_right_cm' => 1.0, + 'margin_header_cm' => 0.5, + 'margin_footer_cm' => 1.5, + 'page_background_image' => '', + 'watermark_type' => 'none', + 'watermark_text' => '', + 'watermark_image' => '', + 'signature_image' => '', + 'watermark_alpha' => 0.12, + 'header_html' => '', + 'first_page_header_html' => '', + 'content_prefix_html' => '', + 'content_suffix_html' => '', + 'footer_html' => '', + 'last_page_footer_html' => '', + ]); + + if ($labType <= 0 || ! $this->db->tableExists('diagnosis_print_templates')) { + return $defaults; + } + + $builder = $this->db->table('diagnosis_print_templates') + ->where('modality', $labType) + ->where('status', 1); + + if ($templateId > 0) { + $builder->where('id', $templateId); + } else { + $builder->orderBy('is_default', 'DESC')->orderBy('id', 'ASC'); + } + + $row = $builder->get(1)->getRowArray(); + if (! is_array($row)) { + return $defaults; + } + + foreach ($defaults as $key => $value) { + if (array_key_exists($key, $row) && $row[$key] !== null && $row[$key] !== '') { + if (is_float($value)) { + $defaults[$key] = (float) $row[$key]; + } else { + $defaults[$key] = (string) $row[$key]; + } + } + } + + $pageSize = strtoupper(trim((string) ($defaults['page_size'] ?? 'A4'))); + + $defaults['id'] = (int) ($row['id'] ?? 0); + if (! in_array($pageSize, ['A4', 'A4-L', 'LETTER', 'LEGAL'], true)) { + $pageSize = 'A4'; + } + $defaults['page_size'] = $pageSize; + + return $defaults; + } + + private function getDiagnosisPrintSetting(int $labType): array + { + $defaults = [ + 'letter_margin_top' => 12.0, + 'letter_margin_left' => 10.0, + 'letter_margin_right' => 10.0, + 'letter_margin_bottom' => 12.0, + 'plain_header_html' => '', + 'plain_background_image' => '', + 'mpdf_prefix_html' => '', + 'mpdf_suffix_html' => '', + 'patient_info_html' => '', + ]; + + if ($labType <= 0 || ! $this->db->tableExists('diagnosis_head_name')) { + return $defaults; + } + + $fields = $this->db->getFieldNames('diagnosis_head_name'); + $select = ['d_type']; + foreach (array_keys($defaults) as $field) { + if (in_array($field, $fields, true)) { + $select[] = $field; + } + } + + $row = $this->db->table('diagnosis_head_name') + ->select(implode(',', $select)) + ->where('d_type', $labType) + ->get(1) + ->getRowArray(); + + if (! is_array($row)) { + return $defaults; + } + + foreach ($defaults as $key => $defaultValue) { + if (array_key_exists($key, $row) && $row[$key] !== null && $row[$key] !== '') { + $defaults[$key] = is_float($defaultValue) ? (float) $row[$key] : (string) $row[$key]; + } + } + + return $defaults; + } + + /** + * Build an associative array of {{token}} => value pairs for PDF template substitution. + * All string values are HTML-escaped so they are safe to embed inside HTML attributes and text. + */ + private function buildPdfTokens(array $data): array + { + $e = static function (string $v): string { + return htmlspecialchars($v, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8'); + }; + + $age = (string) ($data['age'] ?? '-'); + $gender = (string) ($data['gender'] ?? '-'); + $hospitalName = defined('H_Name') ? (string) constant('H_Name') : 'Hospital'; + $hospitalAddress1 = defined('H_address_1') ? (string) constant('H_address_1') : ''; + $hospitalPhone = defined('H_phone_No') ? (string) constant('H_phone_No') : ''; + $hospitalLogoName = defined('H_logo') ? trim((string) constant('H_logo')) : ''; + $hospitalLogoUrl = $this->resolvePdfAssetPath([ + $hospitalLogoName !== '' ? 'assets/images/' . ltrim($hospitalLogoName, '/\\') : '', + 'assets/img/logo.png', + 'assets/images/no_image.svg', + ]); + $signatureImageUrl = $this->resolvePdfAssetPath([ + (string) ($data['signature_image_url'] ?? ''), + 'assets/images/drPreetiSingh.jpg', + ]); + + return [ + '{{invoice_code}}' => $e((string) ($data['invoice_code'] ?? '')), + '{{patient_name}}' => $e((string) ($data['patient_name'] ?? '')), + '{{relative_type}}' => $e((string) ($data['relative_type'] ?? '')), + '{{relative_name}}' => $e((string) ($data['relative_name'] ?? '')), + '{{relative}}' => $e(trim((string) ($data['relative_type'] ?? '') . ' ' . (string) ($data['relative_name'] ?? ''))), + '{{age}}' => $e($age), + '{{gender}}' => $e($gender), + '{{age_sex}}' => $e($age . ' / ' . $gender), + '{{uhid}}' => $e((string) ($data['uhid'] ?? '')), + '{{collected_time}}' => $e((string) ($data['collected_time'] ?? '')), + '{{reported_time}}' => $e((string) ($data['reported_time'] ?? '')), + '{{printed_time}}' => date('d-m-Y h:i A'), + '{{report_title}}' => $e((string) ($data['report_title'] ?? '')), + '{{doctor_name}}' => $e((string) ($data['doctor_name'] ?? '')), + '{{doctor_education}}' => $e((string) ($data['doctor_education'] ?? '')), + '{{technician_name}}' => $e((string) ($data['technician_name'] ?? '')), + '{{hospital_name}}' => $e($hospitalName), + '{{hospital_address_1}}' => $e($hospitalAddress1), + '{{hospital_phone}}' => $e($hospitalPhone), + '{{hospital_logo_name}}' => $e($hospitalLogoName), + '{{hospital_logo_url}}' => $e($hospitalLogoUrl), + '{{hospital_logo_path}}' => $e($hospitalLogoUrl), + '{{signature_image_url}}' => $e($signatureImageUrl), + '{{signature_image_path}}' => $e($signatureImageUrl), + ]; + } + + private function resolvePdfAssetPath(array $candidates): string + { + foreach ($candidates as $candidate) { + $candidate = trim((string) $candidate); + + if ($candidate === '') { + continue; + } + + if (preg_match('/^https?:\/\//i', $candidate) === 1 || str_starts_with($candidate, 'data:')) { + continue; + } + + if (preg_match('/^[A-Za-z]:[\\\\\/]/', $candidate) === 1) { + $absolutePath = $candidate; + } else { + $relativePath = ltrim(str_replace('\\', '/', $candidate), '/'); + $absolutePath = str_starts_with($relativePath, 'public/') + ? ROOTPATH . $relativePath + : FCPATH . $relativePath; + } + + $absolutePath = str_replace('\\', '/', $absolutePath); + + if (is_file($absolutePath)) { + return $absolutePath; + } + } + + return ''; + } + + /** + * Replace all {{token}} placeholders in $html using the token map returned by buildPdfTokens(). + */ + private function applyPdfTokens(string $html, array $tokens): string + { + return str_replace(array_keys($tokens), array_values($tokens), $html); + } + + private function buildDefaultPatientInfoHtml(array $data): string + { + $e = static function (string $v): string { + return htmlspecialchars($v, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8'); + }; + $ageSex = $e((string) ($data['age_text'] ?? '-')) . ' / ' . $e((string) ($data['gender_text'] ?? '-')); + $relative = $e(trim((string) ($data['relative_name'] ?? '') . ' ' . (string) ($data['relative_text'] ?? ''))); + + return '| '
+ . 'Invoice ID: ' . $e((string) ($data['invoice_code'] ?? '')) . ' ' + . 'Patient: ' . $e((string) ($data['patient_name'] ?? '')) . ' ' + . 'Relative: ' . $relative . ' ' + . 'Age/Sex: ' . $ageSex + . ' | '
+ . ''
+ . 'UHID: ' . $e((string) ($data['uhid'] ?? '')) . ' ' + . 'Collected: ' . $e((string) ($data['collected_time'] ?? '')) . ' ' + . 'Reported: ' . $e((string) ($data['reported_time'] ?? '')) . ' ' + . 'Printed: ' . $e(date('d-m-Y h:i A')) + . ' | '
+ . '
No compiled report data available for this request.
'; + } + + $printSetting = $this->getDiagnosisTemplateSetting($labType, $templateId); + + $patientName = trim((string) ($patient->p_fname ?? '')); + $relativeName = trim((string) ($patient->p_relative ?? '')); + $relativeText = trim((string) ($patient->p_rname ?? '')); + $repoTitle = trim((string) ($itemType->group_desc ?? 'Lab Report')); + + $tokens = $this->buildPdfTokens([ + 'invoice_code' => $invoice->invoice_code ?? '', + 'patient_name' => $patientName, + 'relative_type' => $relativeName, + 'relative_name' => $relativeText, + 'age' => $ageText, + 'gender' => $genderText, + 'uhid' => $patient->p_code ?? '', + 'collected_time' => $invoiceRequest->collected_time ?? '', + 'reported_time' => $invoiceRequest->reported_time ?? '', + 'report_title' => $repoTitle, + 'doctor_name' => $head->doc_name ?? '', + 'doctor_education' => $head->doc_edu ?? '', + 'technician_name' => $head->tech_name ?? '', + 'signature_image_url' => (string) ($printSetting['signature_image'] ?? ''), ]); + + $rawPatientInfoHtml = trim((string) ($printSetting['patient_info_html'] ?? '')); + if ($rawPatientInfoHtml !== '') { + $patientInfoHtml = $this->applyPdfTokens($rawPatientInfoHtml, $tokens); + } elseif (($printSetting['id'] ?? 0) <= 0) { + $patientInfoHtml = $this->buildDefaultPatientInfoHtml([ + 'invoice_code' => $invoice->invoice_code ?? '', + 'patient_name' => $patientName, + 'relative_name' => $relativeName, + 'relative_text' => $relativeText, + 'age_text' => $ageText, + 'gender_text' => $genderText, + 'uhid' => $patient->p_code ?? '', + 'collected_time' => $invoiceRequest->collected_time ?? '', + 'reported_time' => $invoiceRequest->reported_time ?? '', + ]); + } else { + $patientInfoHtml = ''; + } + + $reportHtml = trim((string) ($printSetting['content_prefix_html'] ?? '')) . $reportHtml . trim((string) ($printSetting['content_suffix_html'] ?? '')); + + $marginTop = $this->cmToMm($printSetting['page_margin_top_cm'] ?? 1.2, 1.2); + $marginBottom = $this->cmToMm($printSetting['page_margin_bottom_cm'] ?? 1.2, 1.2); + $marginLeft = $this->cmToMm($printSetting['page_margin_left_cm'] ?? 1.0, 1.0); + $marginRight = $this->cmToMm($printSetting['page_margin_right_cm'] ?? 1.0, 1.0); + $marginHeader = $this->cmToMm($printSetting['margin_header_cm'] ?? 0.5, 0.5); + $marginFooter = $this->cmToMm($printSetting['margin_footer_cm'] ?? 1.5, 1.5); + + $pdfHtml = view('diagnosis/pdf_compiled_report', [ + 'invoiceRequest' => $invoiceRequest, + 'invoice' => $invoice, + 'patient' => $patient, + 'reportHead' => $reportHead, + 'reportHtml' => $reportHtml, + 'isPlainPaper' => $isPlainPaper, + 'ageText' => $ageText, + 'genderText' => $genderText, + 'printSetting' => $printSetting, + 'labType' => $labType, + 'invoiceId' => $invoiceId, + 'itemType' => $itemType, + 'patientName' => $patientName, + 'relativeName' => $relativeName, + 'relativeText' => $relativeText, + 'repoTitle' => $repoTitle, + 'patientInfoHtml' => $patientInfoHtml, + ]); + + $rawHeaderHtml = trim((string) ($printSetting['header_html'] ?? '')); + $rawFirstPageHeaderHtml = trim((string) ($printSetting['first_page_header_html'] ?? '')); + $rawFooterHtml = trim((string) ($printSetting['footer_html'] ?? '')); + $rawLastPageFooterHtml = trim((string) ($printSetting['last_page_footer_html'] ?? '')); + + $autoHeaderHtml = $this->buildAutoMpdfHeaderBlock( + $rawHeaderHtml, + $rawFirstPageHeaderHtml, + 'diag_auto_header_compiled' + ); + $autoFooterHtml = $this->buildAutoMpdfFooterBlock( + $rawFooterHtml, + $rawLastPageFooterHtml, + 'diag_auto_footer_compiled' + ); + + $prefixHtml = $this->applyPdfTokens(trim((string) ($printSetting['mpdf_prefix_html'] ?? '')) + . $autoHeaderHtml + . $autoFooterHtml, $tokens); + $suffixHtml = $this->applyPdfTokens(trim((string) ($printSetting['mpdf_suffix_html'] ?? '')), $tokens); + + $mpdfTempDir = WRITEPATH . 'cache' . DIRECTORY_SEPARATOR . 'mpdf'; + if (! is_dir($mpdfTempDir)) { + mkdir($mpdfTempDir, 0755, true); + } + + $t_start = microtime(true); + $mpdf = new Mpdf([ + 'format' => (string) ($printSetting['page_size'] ?? 'A4'), + 'margin_top' => $marginTop, + 'margin_bottom' => $marginBottom, + 'margin_left' => $marginLeft, + 'margin_right' => $marginRight, + 'margin_header' => $marginHeader, + 'margin_footer' => $marginFooter, + 'tempDir' => $mpdfTempDir, + 'autoScriptToLang' => false, + 'autoLanguageDetection' => false, + 'autoArabic' => false, + 'autoVietnamese' => false, + ]); + $t_mpdf_init = microtime(true); + + // For plain HTML (without explicit mPDF tags), set header/footer via API. + $headerHasTags = preg_match('/<\s*(htmlpageheader|sethtmlpageheader)\b/i', $rawHeaderHtml . $rawFirstPageHeaderHtml) === 1; + $footerHasTags = preg_match('/<\s*(htmlpagefooter|sethtmlpagefooter)\b/i', $rawFooterHtml . $rawLastPageFooterHtml) === 1; + + if (! $headerHasTags) { + $plainHeader = $this->applyPdfTokens($rawHeaderHtml . $rawFirstPageHeaderHtml, $tokens); + if ($plainHeader !== '') { + $mpdf->SetHTMLHeader($plainHeader, 'O'); + $mpdf->SetHTMLHeader($plainHeader, 'E'); + } + } + + if (! $footerHasTags) { + $plainFooter = $this->applyPdfTokens($rawFooterHtml, $tokens); + if ($plainFooter === '') { + $plainFooter = $this->applyPdfTokens($rawLastPageFooterHtml, $tokens); + } + + if ($plainFooter !== '') { + $mpdf->SetHTMLFooter($plainFooter, 'O'); + $mpdf->SetHTMLFooter($plainFooter, 'E'); + } + } + + $backgroundImage = trim((string) ($printSetting['page_background_image'] ?? '')); + if ($backgroundImage !== '') { + $absolutePath = FCPATH . ltrim(str_replace('\\', '/', $backgroundImage), '/'); + if (is_file($absolutePath)) { + $mpdf->SetWatermarkImage($absolutePath, 1, [210, 297], 'F'); + $mpdf->showWatermarkImage = true; + } + } + + $watermarkType = (string) ($printSetting['watermark_type'] ?? 'none'); + $watermarkAlpha = (float) ($printSetting['watermark_alpha'] ?? 0.12); + if ($watermarkType === 'text') { + $text = trim((string) ($printSetting['watermark_text'] ?? '')); + if ($text !== '') { + $mpdf->SetWatermarkText($text, $watermarkAlpha); + $mpdf->showWatermarkText = true; + } + } elseif ($watermarkType === 'image') { + $wmImage = trim((string) ($printSetting['watermark_image'] ?? '')); + if ($wmImage !== '') { + $absolutePath = FCPATH . ltrim(str_replace('\\', '/', $wmImage), '/'); + if (is_file($absolutePath)) { + $mpdf->SetWatermarkImage($absolutePath, $watermarkAlpha); + $mpdf->showWatermarkImage = true; + } + } + } + + if ($prefixHtml !== '') { + $mpdf->WriteHTML($prefixHtml, HTMLParserMode::HTML_HEADER_BUFFER); + } + $t_prefix = microtime(true); + + $mpdf->WriteHTML($pdfHtml); + $t_body = microtime(true); + + if ($suffixHtml !== '') { + $mpdf->WriteHTML($suffixHtml, HTMLParserMode::HTML_HEADER_BUFFER, false, false); + } + $t_suffix = microtime(true); + + $invoiceCode = preg_replace('/[^A-Za-z0-9\-_]/', '_', (string) ($invoice->invoice_code ?? 'invoice')); + $fileName = 'Compiled_Report_' . $invoiceCode . '_' . $labType . '.pdf'; + + $pdfOutput = $mpdf->Output($fileName, 'S'); + $t_output = microtime(true); + + log_message('debug', sprintf( + '[PDF_TIMING printPdfCreate] invoice=%s lab=%s | new_Mpdf()=%.2fs | WriteHTML(prefix)=%.2fs | WriteHTML(body)=%.2fs | WriteHTML(suffix)=%.2fs | Output()=%.2fs | TOTAL=%.2fs | body_bytes=%d', + $invoiceId, $labType, + $t_mpdf_init - $t_start, + $t_prefix - $t_mpdf_init, + $t_body - $t_prefix, + $t_suffix - $t_body, + $t_output - $t_suffix, + $t_output - $t_start, + strlen($pdfHtml) + )); + + return $this->response + ->setHeader('Content-Type', 'application/pdf') + ->setHeader('Cache-Control', 'no-store, no-cache, must-revalidate, max-age=0') + ->setHeader('Pragma', 'no-cache') + ->setHeader('Expires', '0') + ->setHeader('Content-Disposition', 'inline; filename="' . $fileName . '"') + ->setBody($pdfOutput); } public function reportFileList($invoiceId, $labType, $delete = 0) diff --git a/app/Controllers/Setting/Template.php b/app/Controllers/Setting/Template.php index e6ca6a0..1f0ab5c 100644 --- a/app/Controllers/Setting/Template.php +++ b/app/Controllers/Setting/Template.php @@ -638,6 +638,318 @@ class Template extends BaseController return $this->response->setBody($html); } + public function diagnosis_print_settings(int $modality = 3) + { + if ($resp = $this->requireAnyPermission(['template.pathology', 'template.ultrasound', 'template.xray', 'template.ct', 'template.mri', 'template.echo'])) { + return $resp; + } + + $modalityList = [ + 5 => 'Pathology', + 1 => 'Ultrasound', + 2 => 'MRI', + 3 => 'X-Ray', + 4 => 'CT-Scan', + 6 => 'Echo', + ]; + + if (! in_array($modality, [1, 2, 3, 4, 5, 6], true)) { + $modality = 3; + } + + $notice = ''; + $noticeType = 'success'; + + if (! $this->db->tableExists('diagnosis_print_templates')) { + return view('Setting/Template/diagnosis_print_settings', [ + 'modality' => $modality, + 'row' => [], + 'templates' => [], + 'selected_template_id' => 0, + 'notice' => 'diagnosis_print_templates table not found. Please run migration.', + 'notice_type' => 'danger', + 'columns_ready' => false, + 'modality_list' => $modalityList, + ]); + } + + $columnsReady = true; + $templateTable = $this->db->table('diagnosis_print_templates'); + $templateFields = $this->db->getFieldNames('diagnosis_print_templates'); + $hasSignatureImageColumn = in_array('signature_image', $templateFields, true); + + // Backward-compatible: if this DB has not yet received the signature column, + // try to add it automatically so template-level signature upload can work. + if (! $hasSignatureImageColumn) { + try { + $this->db->query("ALTER TABLE diagnosis_print_templates ADD COLUMN signature_image VARCHAR(255) NULL AFTER watermark_image"); + $templateFields = $this->db->getFieldNames('diagnosis_print_templates'); + $hasSignatureImageColumn = in_array('signature_image', $templateFields, true); + } catch (\Throwable $e) { + // Keep page usable even when ALTER permission is not available. + $hasSignatureImageColumn = false; + } + } + + $selectedTemplateId = (int) ($this->request->getGet('template_id') ?? 0); + $isNewTemplate = (int) ($this->request->getGet('new') ?? 0) === 1; + + if (strtolower($this->request->getMethod()) === 'post') { + $modality = (int) ($this->request->getPost('modality') ?? $modality); + if (! in_array($modality, [1, 2, 3, 4, 5, 6], true)) { + $modality = 3; + } + + $selectedTemplateId = (int) ($this->request->getPost('template_id') ?? 0); + + if (! $columnsReady) { + $notice = 'Print settings columns are missing. Please run migration first.'; + $noticeType = 'danger'; + } else { + $existing = []; + if ($selectedTemplateId > 0) { + $existing = $templateTable + ->where('id', $selectedTemplateId) + ->where('modality', $modality) + ->get(1) + ->getRowArray() ?? []; + } + + $templateName = trim((string) ($this->request->getPost('template_name') ?? '')); + if ($templateName === '') { + $templateName = 'Template ' . date('d-m-Y H:i'); + } + + $removeBackground = (int) ($this->request->getPost('remove_background') ?? 0) === 1; + $removeWatermarkImage = (int) ($this->request->getPost('remove_watermark_image') ?? 0) === 1; + $removeSignatureImage = (int) ($this->request->getPost('remove_signature_image') ?? 0) === 1; + + $backgroundPath = (string) ($existing['page_background_image'] ?? ''); + $watermarkImagePath = (string) ($existing['watermark_image'] ?? ''); + $signatureImagePath = (string) ($existing['signature_image'] ?? ''); + + if ($removeBackground) { + $backgroundPath = ''; + } + if ($removeWatermarkImage) { + $watermarkImagePath = ''; + } + if ($removeSignatureImage) { + $signatureImagePath = ''; + } + + $bgUpload = $this->request->getFile('page_background_image'); + if ($bgUpload && $bgUpload->isValid() && ! $bgUpload->hasMoved()) { + $ext = strtolower((string) $bgUpload->getExtension()); + if (in_array($ext, ['png', 'jpg', 'jpeg', 'webp'], true)) { + $folder = rtrim(FCPATH, '/\\') . DIRECTORY_SEPARATOR . 'uploads' . DIRECTORY_SEPARATOR . 'diagnosis_print_bg'; + if (! is_dir($folder)) { + @mkdir($folder, 0777, true); + } + + $newName = 'diag_tpl_bg_' . $modality . '_' . date('Ymd_His') . '.' . $ext; + $bgUpload->move($folder, $newName, true); + $backgroundPath = 'uploads/diagnosis_print_bg/' . $newName; + } else { + $notice = 'Background image must be PNG/JPG/JPEG/WEBP.'; + $noticeType = 'danger'; + } + } + + $wmUpload = $this->request->getFile('watermark_image'); + if ($wmUpload && $wmUpload->isValid() && ! $wmUpload->hasMoved()) { + $ext = strtolower((string) $wmUpload->getExtension()); + if (in_array($ext, ['png', 'jpg', 'jpeg', 'webp'], true)) { + $folder = rtrim(FCPATH, '/\\') . DIRECTORY_SEPARATOR . 'uploads' . DIRECTORY_SEPARATOR . 'diagnosis_print_wm'; + if (! is_dir($folder)) { + @mkdir($folder, 0777, true); + } + + $newName = 'diag_tpl_wm_' . $modality . '_' . date('Ymd_His') . '.' . $ext; + $wmUpload->move($folder, $newName, true); + $watermarkImagePath = 'uploads/diagnosis_print_wm/' . $newName; + } else { + $notice = 'Watermark image must be PNG/JPG/JPEG/WEBP.'; + $noticeType = 'danger'; + } + } + + if ($hasSignatureImageColumn) { + $sigUpload = $this->request->getFile('signature_image'); + if ($sigUpload && $sigUpload->isValid() && ! $sigUpload->hasMoved()) { + $ext = strtolower((string) $sigUpload->getExtension()); + if (in_array($ext, ['png', 'jpg', 'jpeg', 'webp'], true)) { + $folder = rtrim(FCPATH, '/\\') . DIRECTORY_SEPARATOR . 'uploads' . DIRECTORY_SEPARATOR . 'diagnosis_print_sign'; + if (! is_dir($folder)) { + @mkdir($folder, 0777, true); + } + + $newName = 'diag_tpl_sign_' . $modality . '_' . date('Ymd_His') . '.' . $ext; + $sigUpload->move($folder, $newName, true); + $signatureImagePath = 'uploads/diagnosis_print_sign/' . $newName; + } else { + $notice = 'Signature image must be PNG/JPG/JPEG/WEBP.'; + $noticeType = 'danger'; + } + } + } + + $data = [ + 'modality' => $modality, + 'template_name' => $templateName, + 'page_size' => trim((string) ($this->request->getPost('page_size') ?? 'A4')) ?: 'A4', + 'page_margin_top_cm' => $this->normalizeMarginCm($this->request->getPost('page_margin_top_cm'), 1.2), + 'page_margin_bottom_cm' => $this->normalizeMarginCm($this->request->getPost('page_margin_bottom_cm'), 1.2), + 'page_margin_left_cm' => $this->normalizeMarginCm($this->request->getPost('page_margin_left_cm'), 1.0), + 'page_margin_right_cm' => $this->normalizeMarginCm($this->request->getPost('page_margin_right_cm'), 1.0), + 'margin_header_cm' => $this->normalizeMarginCm($this->request->getPost('margin_header_cm'), 0.5), + 'margin_footer_cm' => $this->normalizeMarginCm($this->request->getPost('margin_footer_cm'), 1.5), + 'page_background_image' => $backgroundPath, + 'watermark_type' => in_array((string) $this->request->getPost('watermark_type'), ['none', 'text', 'image'], true) + ? (string) $this->request->getPost('watermark_type') + : 'none', + 'watermark_text' => (string) ($this->request->getPost('watermark_text') ?? ''), + 'watermark_image' => $watermarkImagePath, + 'signature_image' => $signatureImagePath, + 'watermark_alpha' => $this->normalizeWatermarkAlpha($this->request->getPost('watermark_alpha')), + 'header_html' => (string) ($this->request->getPost('header_html') ?? ''), + 'first_page_header_html' => (string) ($this->request->getPost('first_page_header_html') ?? ''), + 'content_prefix_html' => (string) ($this->request->getPost('content_prefix_html') ?? ''), + 'content_suffix_html' => (string) ($this->request->getPost('content_suffix_html') ?? ''), + 'footer_html' => (string) ($this->request->getPost('footer_html') ?? ''), + 'last_page_footer_html' => (string) ($this->request->getPost('last_page_footer_html') ?? ''), + 'patient_info_html' => (string) ($this->request->getPost('patient_info_html') ?? ''), + 'mpdf_prefix_html' => (string) ($this->request->getPost('mpdf_prefix_html') ?? ''), + 'mpdf_suffix_html' => (string) ($this->request->getPost('mpdf_suffix_html') ?? ''), + 'is_default' => (int) ($this->request->getPost('is_default') ?? 0) === 1 ? 1 : 0, + 'status' => 1, + ]; + + if (! $hasSignatureImageColumn) { + unset($data['signature_image']); + } + + if ($noticeType !== 'danger') { + if ($data['is_default'] === 1) { + $templateTable + ->where('modality', $modality) + ->set('is_default', 0) + ->update(); + } + + if (! empty($existing) && $selectedTemplateId > 0) { + $templateTable + ->where('id', $selectedTemplateId) + ->where('modality', $modality) + ->update($data); + } else { + $templateTable->insert($data); + $selectedTemplateId = (int) $this->db->insertID(); + } + + $notice = (($modalityList[$modality] ?? 'Diagnosis') . ' print template saved.'); + $noticeType = 'success'; + } + } + + if ($this->request->isAJAX()) { + return $this->response->setJSON([ + 'status' => $noticeType === 'success' ? 'success' : 'error', + 'notice' => $notice, + 'notice_type' => $noticeType, + 'modality' => $modality, + 'selected_template_id' => $selectedTemplateId, + 'csrfName' => csrf_token(), + 'csrfHash' => csrf_hash(), + ]); + } + } + + $templates = $templateTable + ->select('id, template_name, is_default') + ->where('modality', $modality) + ->where('status', 1) + ->orderBy('is_default', 'DESC') + ->orderBy('template_name', 'ASC') + ->get() + ->getResultArray(); + + if (! $isNewTemplate && $selectedTemplateId <= 0 && ! empty($templates)) { + $selectedTemplateId = (int) ($templates[0]['id'] ?? 0); + } + + $row = []; + if ($selectedTemplateId > 0) { + $row = $templateTable + ->where('id', $selectedTemplateId) + ->where('modality', $modality) + ->get(1) + ->getRowArray() ?? []; + } + + return view('Setting/Template/diagnosis_print_settings', [ + 'modality' => $modality, + 'row' => $row, + 'templates' => $templates, + 'selected_template_id' => $selectedTemplateId, + 'notice' => $notice, + 'notice_type' => $noticeType, + 'columns_ready' => $columnsReady, + 'modality_list' => $modalityList, + 'has_signature_image_column' => $hasSignatureImageColumn, + 'has_signature_image_column' => $hasSignatureImageColumn, + ]); + } + + private function normalizeWatermarkAlpha($rawValue): float + { + if ($rawValue === null || $rawValue === '') { + return 0.12; + } + + $value = (float) $rawValue; + if (! is_finite($value)) { + $value = 0.12; + } + + $value = max(0.01, min(1.0, $value)); + + return round($value, 2); + } + + private function normalizeMarginCm($rawValue, float $default): float + { + if ($rawValue === null || $rawValue === '') { + return $default; + } + + $value = (float) $rawValue; + if (! is_finite($value)) { + $value = $default; + } + + // Allow large top margins like 6.1cm for heavy report headers. + $value = max(0.0, min(25.0, $value)); + + return round($value, 2); + } + + private function normalizeMarginMm($rawValue, float $default): float + { + if ($rawValue === null || $rawValue === '') { + return $default; + } + + $value = (float) $rawValue; + if (! is_finite($value)) { + $value = $default; + } + + $value = max(0.0, min(60.0, $value)); + + return round($value, 2); + } + private function renderOptionTable(int $mstTestKey): string { $sql = "select *, if(option_bold=1,'Bold','') as option_bold_str diff --git a/app/Database/Migrations/2026-03-13-000016_AddDiagnosisHeadPrintSettings.php b/app/Database/Migrations/2026-03-13-000016_AddDiagnosisHeadPrintSettings.php new file mode 100644 index 0000000..bb81300 --- /dev/null +++ b/app/Database/Migrations/2026-03-13-000016_AddDiagnosisHeadPrintSettings.php @@ -0,0 +1,131 @@ +db->query("SHOW TABLES LIKE 'diagnosis_head_name'"); + return $query->getNumRows() > 0; + } + + private function diagnosisHeadFields(): array + { + $fields = []; + $query = $this->db->query("SHOW COLUMNS FROM diagnosis_head_name"); + foreach ($query->getResultArray() as $row) { + $fields[] = (string) ($row['Field'] ?? ''); + } + + return $fields; + } + + public function up() + { + if (! $this->hasDiagnosisHeadTable()) { + return; + } + + $fields = $this->diagnosisHeadFields(); + + if (! in_array('letter_margin_top', $fields, true)) { + $this->forge->addColumn('diagnosis_head_name', [ + 'letter_margin_top' => [ + 'type' => 'decimal', + 'constraint' => '6,2', + 'default' => '12.00', + 'null' => true, + 'after' => 'print_page_direct', + ], + ]); + } + + if (! in_array('letter_margin_left', $fields, true)) { + $this->forge->addColumn('diagnosis_head_name', [ + 'letter_margin_left' => [ + 'type' => 'decimal', + 'constraint' => '6,2', + 'default' => '10.00', + 'null' => true, + 'after' => 'letter_margin_top', + ], + ]); + } + + if (! in_array('letter_margin_right', $fields, true)) { + $this->forge->addColumn('diagnosis_head_name', [ + 'letter_margin_right' => [ + 'type' => 'decimal', + 'constraint' => '6,2', + 'default' => '10.00', + 'null' => true, + 'after' => 'letter_margin_left', + ], + ]); + } + + if (! in_array('letter_margin_bottom', $fields, true)) { + $this->forge->addColumn('diagnosis_head_name', [ + 'letter_margin_bottom' => [ + 'type' => 'decimal', + 'constraint' => '6,2', + 'default' => '12.00', + 'null' => true, + 'after' => 'letter_margin_right', + ], + ]); + } + + if (! in_array('plain_header_html', $fields, true)) { + $this->forge->addColumn('diagnosis_head_name', [ + 'plain_header_html' => [ + 'type' => 'longtext', + 'null' => true, + 'after' => 'letter_margin_bottom', + ], + ]); + } + + if (! in_array('plain_background_image', $fields, true)) { + $this->forge->addColumn('diagnosis_head_name', [ + 'plain_background_image' => [ + 'type' => 'varchar', + 'constraint' => 255, + 'null' => true, + 'after' => 'plain_header_html', + ], + ]); + } + } + + public function down() + { + if (! $this->hasDiagnosisHeadTable()) { + return; + } + + $fields = $this->diagnosisHeadFields(); + + if (in_array('plain_background_image', $fields, true)) { + $this->forge->dropColumn('diagnosis_head_name', 'plain_background_image'); + } + if (in_array('plain_header_html', $fields, true)) { + $this->forge->dropColumn('diagnosis_head_name', 'plain_header_html'); + } + if (in_array('letter_margin_bottom', $fields, true)) { + $this->forge->dropColumn('diagnosis_head_name', 'letter_margin_bottom'); + } + if (in_array('letter_margin_right', $fields, true)) { + $this->forge->dropColumn('diagnosis_head_name', 'letter_margin_right'); + } + if (in_array('letter_margin_left', $fields, true)) { + $this->forge->dropColumn('diagnosis_head_name', 'letter_margin_left'); + } + if (in_array('letter_margin_top', $fields, true)) { + $this->forge->dropColumn('diagnosis_head_name', 'letter_margin_top'); + } + } +} diff --git a/app/Database/Migrations/2026-03-14-000017_AddDiagnosisHeadMpdfBlocks.php b/app/Database/Migrations/2026-03-14-000017_AddDiagnosisHeadMpdfBlocks.php new file mode 100644 index 0000000..34bfb75 --- /dev/null +++ b/app/Database/Migrations/2026-03-14-000017_AddDiagnosisHeadMpdfBlocks.php @@ -0,0 +1,71 @@ +db->query("SHOW TABLES LIKE 'diagnosis_head_name'"); + return $query->getNumRows() > 0; + } + + private function diagnosisHeadFields(): array + { + $fields = []; + $query = $this->db->query("SHOW COLUMNS FROM diagnosis_head_name"); + foreach ($query->getResultArray() as $row) { + $fields[] = (string) ($row['Field'] ?? ''); + } + + return $fields; + } + + public function up() + { + if (! $this->hasDiagnosisHeadTable()) { + return; + } + + $fields = $this->diagnosisHeadFields(); + + if (! in_array('mpdf_prefix_html', $fields, true)) { + $this->forge->addColumn('diagnosis_head_name', [ + 'mpdf_prefix_html' => [ + 'type' => 'longtext', + 'null' => true, + 'after' => 'plain_background_image', + ], + ]); + } + + if (! in_array('mpdf_suffix_html', $fields, true)) { + $this->forge->addColumn('diagnosis_head_name', [ + 'mpdf_suffix_html' => [ + 'type' => 'longtext', + 'null' => true, + 'after' => 'mpdf_prefix_html', + ], + ]); + } + } + + public function down() + { + if (! $this->hasDiagnosisHeadTable()) { + return; + } + + $fields = $this->diagnosisHeadFields(); + + if (in_array('mpdf_suffix_html', $fields, true)) { + $this->forge->dropColumn('diagnosis_head_name', 'mpdf_suffix_html'); + } + + if (in_array('mpdf_prefix_html', $fields, true)) { + $this->forge->dropColumn('diagnosis_head_name', 'mpdf_prefix_html'); + } + } +} diff --git a/app/Database/Migrations/2026-03-14-000018_AddDiagnosisPatientInfoHtml.php b/app/Database/Migrations/2026-03-14-000018_AddDiagnosisPatientInfoHtml.php new file mode 100644 index 0000000..bad7097 --- /dev/null +++ b/app/Database/Migrations/2026-03-14-000018_AddDiagnosisPatientInfoHtml.php @@ -0,0 +1,57 @@ +db->query("SHOW TABLES LIKE 'diagnosis_head_name'"); + return $query->getNumRows() > 0; + } + + private function diagnosisHeadFields(): array + { + $fields = []; + $query = $this->db->query("SHOW COLUMNS FROM diagnosis_head_name"); + foreach ($query->getResultArray() as $row) { + $fields[] = (string) ($row['Field'] ?? ''); + } + + return $fields; + } + + public function up() + { + if (! $this->hasDiagnosisHeadTable()) { + return; + } + + $fields = $this->diagnosisHeadFields(); + + if (! in_array('patient_info_html', $fields, true)) { + $this->forge->addColumn('diagnosis_head_name', [ + 'patient_info_html' => [ + 'type' => 'longtext', + 'null' => true, + 'after' => 'mpdf_suffix_html', + ], + ]); + } + } + + public function down() + { + if (! $this->hasDiagnosisHeadTable()) { + return; + } + + $fields = $this->diagnosisHeadFields(); + + if (in_array('patient_info_html', $fields, true)) { + $this->forge->dropColumn('diagnosis_head_name', 'patient_info_html'); + } + } +} diff --git a/app/Database/Migrations/2026-03-14-000019_CreateDiagnosisPrintTemplates.php b/app/Database/Migrations/2026-03-14-000019_CreateDiagnosisPrintTemplates.php new file mode 100644 index 0000000..565be44 --- /dev/null +++ b/app/Database/Migrations/2026-03-14-000019_CreateDiagnosisPrintTemplates.php @@ -0,0 +1,128 @@ +db->query("SHOW TABLES LIKE 'diagnosis_print_templates'"); + return $query->getNumRows() > 0; + } + + public function up() + { + if ($this->hasTemplateTable()) { + return; + } + + $this->forge->addField([ + 'id' => [ + 'type' => 'INT', + 'constraint' => 11, + 'unsigned' => true, + 'auto_increment' => true, + ], + 'modality' => [ + 'type' => 'INT', + 'constraint' => 11, + 'default' => 3, + ], + 'template_name' => [ + 'type' => 'VARCHAR', + 'constraint' => 120, + ], + 'is_default' => [ + 'type' => 'TINYINT', + 'constraint' => 1, + 'default' => 0, + ], + 'status' => [ + 'type' => 'TINYINT', + 'constraint' => 1, + 'default' => 1, + ], + 'page_size' => [ + 'type' => 'VARCHAR', + 'constraint' => 20, + 'default' => 'A4', + ], + 'page_background_image' => [ + 'type' => 'VARCHAR', + 'constraint' => 255, + 'null' => true, + ], + 'watermark_type' => [ + 'type' => 'VARCHAR', + 'constraint' => 10, + 'default' => 'none', + ], + 'watermark_text' => [ + 'type' => 'VARCHAR', + 'constraint' => 255, + 'null' => true, + ], + 'watermark_image' => [ + 'type' => 'VARCHAR', + 'constraint' => 255, + 'null' => true, + ], + 'watermark_alpha' => [ + 'type' => 'DECIMAL', + 'constraint' => '4,2', + 'default' => '0.12', + ], + 'header_html' => [ + 'type' => 'LONGTEXT', + 'null' => true, + ], + 'first_page_header_html' => [ + 'type' => 'LONGTEXT', + 'null' => true, + ], + 'content_prefix_html' => [ + 'type' => 'LONGTEXT', + 'null' => true, + ], + 'content_suffix_html' => [ + 'type' => 'LONGTEXT', + 'null' => true, + ], + 'footer_html' => [ + 'type' => 'LONGTEXT', + 'null' => true, + ], + 'last_page_footer_html' => [ + 'type' => 'LONGTEXT', + 'null' => true, + ], + 'patient_info_html' => [ + 'type' => 'LONGTEXT', + 'null' => true, + ], + 'mpdf_prefix_html' => [ + 'type' => 'LONGTEXT', + 'null' => true, + ], + 'mpdf_suffix_html' => [ + 'type' => 'LONGTEXT', + 'null' => true, + ], + 'created_at DATETIME DEFAULT CURRENT_TIMESTAMP', + 'updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP', + ]); + + $this->forge->addKey('id', true); + $this->forge->addKey(['modality', 'status']); + $this->forge->createTable('diagnosis_print_templates', true, ['ENGINE' => 'InnoDB', 'DEFAULT CHARSET' => 'utf8mb4']); + } + + public function down() + { + if ($this->hasTemplateTable()) { + $this->forge->dropTable('diagnosis_print_templates', true); + } + } +} diff --git a/app/Database/Migrations/2026-03-15-000020_AddDiagnosisTemplateMarginsCm.php b/app/Database/Migrations/2026-03-15-000020_AddDiagnosisTemplateMarginsCm.php new file mode 100644 index 0000000..b6402b9 --- /dev/null +++ b/app/Database/Migrations/2026-03-15-000020_AddDiagnosisTemplateMarginsCm.php @@ -0,0 +1,117 @@ +db->query("SHOW TABLES LIKE 'diagnosis_print_templates'"); + return $query->getNumRows() > 0; + } + + private function templateFields(): array + { + $fields = []; + $query = $this->db->query("SHOW COLUMNS FROM diagnosis_print_templates"); + foreach ($query->getResultArray() as $row) { + $fields[] = (string) ($row['Field'] ?? ''); + } + + return $fields; + } + + public function up() + { + if (! $this->hasTemplateTable()) { + return; + } + + $fields = $this->templateFields(); + + $add = []; + if (! in_array('page_margin_top_cm', $fields, true)) { + $add['page_margin_top_cm'] = [ + 'type' => 'decimal', + 'constraint' => '6,2', + 'null' => true, + 'default' => '1.20', + 'after' => 'page_size', + ]; + } + if (! in_array('page_margin_bottom_cm', $fields, true)) { + $add['page_margin_bottom_cm'] = [ + 'type' => 'decimal', + 'constraint' => '6,2', + 'null' => true, + 'default' => '1.20', + 'after' => 'page_margin_top_cm', + ]; + } + if (! in_array('page_margin_left_cm', $fields, true)) { + $add['page_margin_left_cm'] = [ + 'type' => 'decimal', + 'constraint' => '6,2', + 'null' => true, + 'default' => '1.00', + 'after' => 'page_margin_bottom_cm', + ]; + } + if (! in_array('page_margin_right_cm', $fields, true)) { + $add['page_margin_right_cm'] = [ + 'type' => 'decimal', + 'constraint' => '6,2', + 'null' => true, + 'default' => '1.00', + 'after' => 'page_margin_left_cm', + ]; + } + if (! in_array('margin_header_cm', $fields, true)) { + $add['margin_header_cm'] = [ + 'type' => 'decimal', + 'constraint' => '6,2', + 'null' => true, + 'default' => '0.50', + 'after' => 'page_margin_right_cm', + ]; + } + if (! in_array('margin_footer_cm', $fields, true)) { + $add['margin_footer_cm'] = [ + 'type' => 'decimal', + 'constraint' => '6,2', + 'null' => true, + 'default' => '1.50', + 'after' => 'margin_header_cm', + ]; + } + + if (! empty($add)) { + $this->forge->addColumn('diagnosis_print_templates', $add); + } + } + + public function down() + { + if (! $this->hasTemplateTable()) { + return; + } + + $fields = $this->templateFields(); + $dropList = [ + 'page_margin_top_cm', + 'page_margin_bottom_cm', + 'page_margin_left_cm', + 'page_margin_right_cm', + 'margin_header_cm', + 'margin_footer_cm', + ]; + + foreach ($dropList as $field) { + if (in_array($field, $fields, true)) { + $this->forge->dropColumn('diagnosis_print_templates', $field); + } + } + } +} diff --git a/app/Views/Setting/Template/diagnosis_print_settings.php b/app/Views/Setting/Template/diagnosis_print_settings.php new file mode 100644 index 0000000..ad31b72 --- /dev/null +++ b/app/Views/Setting/Template/diagnosis_print_settings.php @@ -0,0 +1,362 @@ + + +