Fix diagnosis print template dual-PDF flow and settings save bug

- Diagnosis.php: resolve doctor mapping via ABDM Report Doctor Mapping,
  compile pathology/diagnosis reports into separate Letter Head and
  Plain Paper PDFs, add compiled-pdf-links endpoint
- Routes.php: add diagnosis/compiled-pdf-links route
- pathology_detail.php: render/persist dual compiled PDF buttons
- Setting/Template.php: per-modality Letter Head / Plain Paper template
  mapping via hospital_setting keys
- Setting/Template/index.php: fix tile links using load_form (which only
  replaces #main) instead of load_form_div, which broke every internal
  AJAX reload (template type switch, modality switch, reset) on the
  diagnosis/discharge/document print template settings pages
- diagnosis_print_settings.php: fix corrupted watermark_alpha number
  input (missing step attribute) that silently blocked form submission
  for templates with non-integer-step legacy values
This commit is contained in:
DevSoftTech Server 2026-08-03 02:43:50 +05:30
parent f57da52725
commit e473be3ae7
6 changed files with 737 additions and 71 deletions

View File

@ -1102,6 +1102,7 @@ $routes->match(['GET', 'POST'], 'diagnosis/get-template-xray/(:num)', 'Diagnosis
$routes->post('diagnosis/remove-test/(:num)', 'Diagnosis::removeTest/$1');
$routes->post('diagnosis/update-combine-report', 'Diagnosis::updateCombineReport');
$routes->post('diagnosis/report-compile/(:num)/(:num)', 'Diagnosis::reportCompile/$1/$2');
$routes->get('diagnosis/compiled-pdf-links/(:num)/(:num)', 'Diagnosis::compiledPdfLinks/$1/$2');
$routes->post('diagnosis/upload-report-file', 'Diagnosis::uploadReportFile');
$routes->get('diagnosis/report-file-list/(:num)/(:num)', 'Diagnosis::reportFileList/$1/$2');
$routes->get('diagnosis/report-file-list/(:num)/(:num)/(:num)', 'Diagnosis::reportFileList/$1/$2/$3');

View File

@ -1428,6 +1428,8 @@ class Diagnosis extends BaseController
->get(1)
->getRow();
$doctorTokenData = $this->resolveMappedReportDoctorTokenData((int) ($row->lab_type ?? 0), $headRow);
$tokens = $this->buildPdfTokens([
'invoice_code' => $row->invoice_code ?? '',
'patient_name' => $patientName,
@ -1439,9 +1441,9 @@ class Diagnosis extends BaseController
'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 ?? '',
'doctor_name' => $doctorTokenData['doctor_name'] ?? '',
'doctor_education' => $doctorTokenData['doctor_education'] ?? '',
'technician_name' => $doctorTokenData['technician_name'] ?? '',
'signature_image_url' => (string) ($printSetting['signature_image'] ?? ''),
]);
@ -1655,17 +1657,37 @@ class Diagnosis extends BaseController
return $defaults;
}
$builder = $this->db->table('diagnosis_print_templates')
->where('modality', $labType)
->where('status', 1);
$row = null;
// When template_id is explicitly requested, honor it first.
// Some legacy flows may save/select template IDs across modality switches.
if ($templateId > 0) {
$builder->where('id', $templateId);
} else {
$builder->orderBy('is_default', 'DESC')->orderBy('id', 'ASC');
$row = $this->db->table('diagnosis_print_templates')
->where('modality', $labType)
->where('status', 1)
->where('id', $templateId)
->get(1)
->getRowArray();
if (! is_array($row)) {
$row = $this->db->table('diagnosis_print_templates')
->where('status', 1)
->where('id', $templateId)
->get(1)
->getRowArray();
}
}
if (! is_array($row)) {
$row = $this->db->table('diagnosis_print_templates')
->where('modality', $labType)
->where('status', 1)
->orderBy('is_default', 'DESC')
->orderBy('id', 'ASC')
->get(1)
->getRowArray();
}
$row = $builder->get(1)->getRowArray();
if (! is_array($row)) {
return $defaults;
}
@ -1736,6 +1758,146 @@ class Diagnosis extends BaseController
return $defaults;
}
/**
* Resolve report doctor token values using ABDM mapping first, with legacy fallback.
*
* @param object|array<string,mixed>|null $legacyHeadRow
* @return array<string,string>
*/
private function resolveMappedReportDoctorTokenData(int $labType, $legacyHeadRow = null): array
{
$legacyDoctorName = '';
$legacyDoctorEducation = '';
$legacyTechnicianName = '';
if (is_object($legacyHeadRow)) {
$legacyDoctorName = trim((string) ($legacyHeadRow->doc_name ?? ''));
$legacyDoctorEducation = trim((string) ($legacyHeadRow->doc_edu ?? ''));
$legacyTechnicianName = trim((string) ($legacyHeadRow->tech_name ?? ''));
} elseif (is_array($legacyHeadRow)) {
$legacyDoctorName = trim((string) ($legacyHeadRow['doc_name'] ?? ''));
$legacyDoctorEducation = trim((string) ($legacyHeadRow['doc_edu'] ?? ''));
$legacyTechnicianName = trim((string) ($legacyHeadRow['tech_name'] ?? ''));
}
$out = [
'doctor_name' => $legacyDoctorName,
'doctor_education' => $legacyDoctorEducation,
'technician_name' => $legacyTechnicianName,
];
$mappedDoctorId = $this->resolveMappedDoctorIdForLabType($labType);
if ($mappedDoctorId <= 0) {
return $out;
}
$mappedDoctor = $this->loadMappedDoctorById($mappedDoctorId);
if (! is_array($mappedDoctor)) {
return $out;
}
$mappedName = trim((string) ($mappedDoctor['name'] ?? ''));
$mappedEducation = trim((string) ($mappedDoctor['education'] ?? ''));
if ($mappedName !== '') {
$out['doctor_name'] = $mappedName;
}
if ($mappedEducation !== '') {
$out['doctor_education'] = $mappedEducation;
}
return $out;
}
private function resolveMappedDoctorIdForLabType(int $labType): int
{
$keys = [];
switch ($labType) {
case 5:
$keys = ['ABDM_DOC_PATHOLOGY'];
break;
case 3:
$keys = ['ABDM_DOC_XRAY', 'ABDM_DOC_RADIOLOGY'];
break;
case 4:
$keys = ['ABDM_DOC_CTSCAN', 'ABDM_DOC_RADIOLOGY'];
break;
case 1:
$keys = ['ABDM_DOC_ULTRASOUND', 'ABDM_DOC_RADIOLOGY'];
break;
case 2:
$keys = ['ABDM_DOC_MRI', 'ABDM_DOC_RADIOLOGY'];
break;
case 6:
$keys = ['ABDM_DOC_RADIOLOGY'];
break;
default:
$keys = ['ABDM_DOC_PATHOLOGY', 'ABDM_DOC_RADIOLOGY'];
break;
}
foreach ($keys as $key) {
$id = (int) $this->readSetting($key);
if ($id > 0) {
return $id;
}
}
return 0;
}
/**
* @return array<string,string>|null
*/
private function loadMappedDoctorById(int $doctorId): ?array
{
if ($doctorId <= 0 || ! $this->db->tableExists('doctor_master')) {
return null;
}
$fields = $this->db->getFieldNames('doctor_master') ?? [];
$select = ['id'];
foreach (['p_title', 'p_fname', 'p_lname', 'education', 'qualification', 'degree', 'speciality', 'specialty', 'designation'] as $field) {
if (in_array($field, $fields, true)) {
$select[] = $field;
}
}
$row = $this->db->table('doctor_master')
->select(implode(',', array_unique($select)))
->where('id', $doctorId)
->get(1)
->getRowArray() ?? [];
if (empty($row)) {
return null;
}
$title = trim((string) ($row['p_title'] ?? ''));
$first = trim((string) ($row['p_fname'] ?? ''));
$last = trim((string) ($row['p_lname'] ?? ''));
$name = trim($title . ' ' . $first . ' ' . $last);
if ($name === '') {
$name = trim($first . ' ' . $last);
}
$education = '';
foreach (['education', 'qualification', 'degree', 'speciality', 'specialty', 'designation'] as $field) {
$value = trim((string) ($row[$field] ?? ''));
if ($value !== '') {
$education = $value;
break;
}
}
return [
'name' => $name,
'education' => $education,
];
}
/**
* 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.
@ -2150,19 +2312,29 @@ class Diagnosis extends BaseController
'report_compile' => $compiledBy,
]);
$pdfStore = $this->autoStoreCompiledPdfAttachment($invoiceId, $labType, $rawData, $header);
$pdfStore = $this->autoStoreCompiledPdfAttachments($invoiceId, $labType, $rawData, $header);
$message = 'Data Compile';
if (($pdfStore['ok'] ?? false) && ! empty($pdfStore['file_id'])) {
$message .= ' + PDF stored';
} elseif (! empty($pdfStore['error'])) {
$message .= ' (PDF store skipped: ' . (string) $pdfStore['error'] . ')';
$storedCount = (int) count((array) ($pdfStore['file_ids'] ?? []));
if ($storedCount > 0) {
$message .= ' + ' . $storedCount . ' PDF stored (Letter Head + Plain Paper)';
}
if (! empty($pdfStore['errors']) && is_array($pdfStore['errors'])) {
$message .= ' (some skipped: ' . implode(' | ', array_values($pdfStore['errors'])) . ')';
}
$compileTemplateIds = $this->resolveCompileTemplateIds($labType);
$compilePrintUrls = $this->buildCompilePrintUrls($invoiceId, $labType, $compileTemplateIds);
$compilePrintLabels = $this->buildCompileTemplateLabels($compileTemplateIds);
return $this->response->setJSON([
'status' => 'success',
'message' => $message,
'pdf_file_id' => (int) ($pdfStore['file_id'] ?? 0),
'pdf_file_ids' => (array) ($pdfStore['file_ids'] ?? []),
'pdf_file_urls' => $compilePrintUrls,
'pdf_file_labels' => $compilePrintLabels,
'pdf_file_urls_saved' => (array) ($pdfStore['file_urls'] ?? []),
]);
} catch (\Throwable $e) {
return $this->response->setJSON([
@ -2173,13 +2345,77 @@ class Diagnosis extends BaseController
}
/**
* Generate compiled report PDF and persist metadata in file_upload_data.
* Generate two compiled report PDFs (letterhead + plain) and persist metadata.
* Fail-open: returns error details without throwing so compile flow stays successful.
*
* @return array{ok: bool, file_id?: int, error?: string}
* @return array{ok: bool, file_id?: int, file_ids?: array<string,int>, file_urls?: array<string,string>, errors?: array<string,string>}
*/
private function autoStoreCompiledPdfAttachment(int $invoiceId, int $labType, string $reportHtml, string $reportHeader = ''): array
private function autoStoreCompiledPdfAttachments(int $invoiceId, int $labType, string $reportHtml, string $reportHeader = ''): array
{
$variants = [
'letterhead' => [
'label' => 'Letter Head',
// On pre-printed letter head paper, avoid injecting extra top patient block.
'include_header' => false,
],
'plain' => [
'label' => 'Plain Paper',
// Plain paper needs generated report header details.
'include_header' => true,
],
];
$fileIds = [];
$fileUrls = [];
$errors = [];
$firstFileId = 0;
foreach ($variants as $variantKey => $cfg) {
$single = $this->autoStoreCompiledPdfAttachmentVariant(
$invoiceId,
$labType,
$reportHtml,
$reportHeader,
(string) $variantKey,
(string) ($cfg['label'] ?? $variantKey),
(bool) ($cfg['include_header'] ?? false)
);
if (($single['ok'] ?? false) && ! empty($single['file_id'])) {
$fileId = (int) $single['file_id'];
$fileIds[$variantKey] = $fileId;
if (! empty($single['file_url'])) {
$fileUrls[$variantKey] = (string) $single['file_url'];
}
if ($firstFileId <= 0) {
$firstFileId = $fileId;
}
} else {
$errors[$variantKey] = (string) ($single['error'] ?? 'store_failed');
}
}
return [
'ok' => ! empty($fileIds),
'file_id' => $firstFileId,
'file_ids' => $fileIds,
'file_urls' => $fileUrls,
'errors' => $errors,
];
}
/**
* @return array{ok: bool, file_id?: int, file_url?: string, error?: string}
*/
private function autoStoreCompiledPdfAttachmentVariant(
int $invoiceId,
int $labType,
string $reportHtml,
string $reportHeader,
string $variantKey,
string $variantLabel,
bool $includeHeader
): array {
if ($invoiceId <= 0 || $labType <= 0) {
return ['ok' => false, 'error' => 'invalid_context'];
}
@ -2189,8 +2425,13 @@ class Diagnosis extends BaseController
}
try {
$compiledHtml = trim($reportHeader) . "\n" . trim($reportHtml);
if ($compiledHtml === '') {
$compiledHtml = '';
if ($includeHeader) {
$compiledHtml .= trim($reportHeader) . "\n";
}
$compiledHtml .= trim($reportHtml);
if (trim($compiledHtml) === '') {
return ['ok' => false, 'error' => 'empty_compiled_report'];
}
@ -2220,7 +2461,7 @@ class Diagnosis extends BaseController
$invoiceCode = trim((string) ($inv['invoice_code'] ?? ''));
}
$invoiceCodeSafe = preg_replace('/[^A-Za-z0-9\-_]/', '_', $invoiceCode !== '' ? $invoiceCode : ('INV_' . $invoiceId));
$storedName = 'compiled_' . $invoiceCodeSafe . '_L' . $labType . '_' . date('Ymd_His') . '.pdf';
$storedName = 'compiled_' . $invoiceCodeSafe . '_L' . $labType . '_' . $variantKey . '_' . date('Ymd_His') . '.pdf';
$targetDir = ROOTPATH . 'public' . DIRECTORY_SEPARATOR . 'uploads' . DIRECTORY_SEPARATOR . 'diagnosis' . DIRECTORY_SEPARATOR . date('Y') . DIRECTORY_SEPARATOR . date('m');
if (! is_dir($targetDir) && ! @mkdir($targetDir, 0775, true) && ! is_dir($targetDir)) {
@ -2236,12 +2477,12 @@ class Diagnosis extends BaseController
$fields = $this->db->getFieldNames('file_upload_data');
// Keep only one latest auto-compiled PDF per invoice/lab context.
// Keep only one latest auto-compiled PDF per invoice/lab context + variant.
if (in_array('scan_type', $fields, true) && in_array('isdelete', $fields, true)) {
$this->db->table('file_upload_data')
->where('charge_id', $invoiceId)
->where('charge_type', $labType)
->where('scan_type', 'compiled_pdf_auto')
->where('scan_type', 'compiled_pdf_auto_' . $variantKey)
->where('isdelete', 0)
->update(['isdelete' => 1]);
}
@ -2254,8 +2495,8 @@ class Diagnosis extends BaseController
$fieldMap = [
'name' => $storedName,
'file_name' => $storedName,
'orig_name' => 'Compiled_Report_' . ($invoiceCode !== '' ? $invoiceCode : $invoiceId) . '_' . $labType . '.pdf',
'client_name' => 'Compiled_Report_' . ($invoiceCode !== '' ? $invoiceCode : $invoiceId) . '_' . $labType . '.pdf',
'orig_name' => 'Compiled_Report_' . ($invoiceCode !== '' ? $invoiceCode : $invoiceId) . '_' . $labType . '_' . $variantKey . '.pdf',
'client_name' => 'Compiled_Report_' . ($invoiceCode !== '' ? $invoiceCode : $invoiceId) . '_' . $labType . '_' . $variantKey . '.pdf',
'file_ext' => '.pdf',
'file_type' => 'application/pdf',
'full_path' => str_replace('\\', '/', $absolutePath),
@ -2267,17 +2508,17 @@ class Diagnosis extends BaseController
'image_type' => 'pdf',
'insert_date' => Time::now('Asia/Kolkata')->toDateTimeString(),
'insert_time' => Time::now('Asia/Kolkata')->toDateTimeString(),
'file_desc' => 'Auto compiled report PDF',
'file_desc' => 'Auto compiled report PDF (' . $variantLabel . ')',
'repo_id' => 0,
'charge_id' => $invoiceId,
'charge_type' => $labType,
'upload_by' => $uploadBy,
'upload_by_id' => $uploadById,
'scan_type' => 'compiled_pdf_auto',
'scan_type' => 'compiled_pdf_auto_' . $variantKey,
'show_type' => 0,
'isdelete' => 0,
'document_type' => 'diagnosis-compiled-report',
'content_description' => 'Auto-generated from diagnosis/report-compile',
'document_type' => 'diagnosis-compiled-report-' . $variantKey,
'content_description' => 'Auto-generated from diagnosis/report-compile (' . $variantLabel . ')',
];
foreach ($fieldMap as $key => $value) {
@ -2291,7 +2532,18 @@ class Diagnosis extends BaseController
return ['ok' => false, 'error' => 'Unable to save compiled PDF metadata'];
}
return ['ok' => true, 'file_id' => (int) $this->db->insertID()];
$publicRoot = rtrim(str_replace('\\', '/', ROOTPATH . 'public'), '/');
$absoluteNormalized = str_replace('\\', '/', $absolutePath);
$relativePublicPath = str_starts_with($absoluteNormalized, $publicRoot)
? ltrim(substr($absoluteNormalized, strlen($publicRoot)), '/')
: '';
$fileUrl = $relativePublicPath !== '' ? base_url($relativePublicPath) : '';
return [
'ok' => true,
'file_id' => (int) $this->db->insertID(),
'file_url' => $fileUrl,
];
} catch (\Throwable $e) {
return ['ok' => false, 'error' => $e->getMessage()];
}
@ -2367,6 +2619,8 @@ class Diagnosis extends BaseController
$relativeText = trim((string) ($patient->p_rname ?? ''));
$repoTitle = trim((string) ($itemType->group_desc ?? 'Lab Report'));
$doctorTokenData = $this->resolveMappedReportDoctorTokenData($labType, $head);
$tokens = $this->buildPdfTokens([
'invoice_code' => $invoice->invoice_code ?? '',
'patient_name' => $patientName,
@ -2378,9 +2632,9 @@ class Diagnosis extends BaseController
'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 ?? '',
'doctor_name' => $doctorTokenData['doctor_name'] ?? '',
'doctor_education' => $doctorTokenData['doctor_education'] ?? '',
'technician_name' => $doctorTokenData['technician_name'] ?? '',
'signature_image_url' => (string) ($printSetting['signature_image'] ?? ''),
]);
@ -2602,6 +2856,137 @@ class Diagnosis extends BaseController
]);
}
public function compiledPdfLinks($invoiceId, $labType)
{
$invoiceId = (int) $invoiceId;
$labType = (int) $labType;
if ($invoiceId <= 0 || $labType <= 0) {
return $this->response->setJSON([
'status' => 'error',
'message' => 'Invalid parameters',
'pdf_file_urls' => [],
]);
}
$templateIds = $this->resolveCompileTemplateIds($labType);
$urls = $this->buildCompilePrintUrls($invoiceId, $labType, $templateIds);
$labels = $this->buildCompileTemplateLabels($templateIds);
return $this->response->setJSON([
'status' => 'success',
'pdf_file_urls' => $urls,
'pdf_file_labels' => $labels,
]);
}
/**
* @return array{letterhead:string, plain:string}
*/
private function buildCompilePrintUrls(int $invoiceId, int $labType, array $templateIds = []): array
{
if (empty($templateIds)) {
$templateIds = $this->resolveCompileTemplateIds($labType);
}
$base = base_url('Lab_Admin/print_pdf_create/' . $invoiceId . '/' . $labType . '/1');
$letterheadUrl = $base;
if (($templateIds['letterhead'] ?? 0) > 0) {
$letterheadUrl .= '?template_id=' . (int) $templateIds['letterhead'];
}
$plainUrl = $base;
if (($templateIds['plain'] ?? 0) > 0) {
$plainUrl .= '?template_id=' . (int) $templateIds['plain'];
}
return [
'letterhead' => $letterheadUrl,
'plain' => $plainUrl,
];
}
/**
* @param array{letterhead:int, plain:int} $templateIds
* @return array{letterhead:string, plain:string}
*/
private function buildCompileTemplateLabels(array $templateIds): array
{
$letterheadName = $this->getDiagnosisTemplateNameById((int) ($templateIds['letterhead'] ?? 0));
$plainName = $this->getDiagnosisTemplateNameById((int) ($templateIds['plain'] ?? 0));
return [
'letterhead' => $letterheadName !== '' ? $letterheadName : 'Default Template',
'plain' => $plainName !== '' ? $plainName : 'Default Template',
];
}
private function getDiagnosisTemplateNameById(int $templateId): string
{
if ($templateId <= 0 || ! $this->db->tableExists('diagnosis_print_templates')) {
return '';
}
$row = $this->db->table('diagnosis_print_templates')
->select('template_name')
->where('id', $templateId)
->where('status', 1)
->get(1)
->getRowArray();
return trim((string) ($row['template_name'] ?? ''));
}
/**
* @return array{letterhead:int, plain:int}
*/
private function resolveCompileTemplateIds(int $labType): array
{
$letterhead = (int) $this->readSetting('DIAG_COMPILE_TPL_LETTERHEAD_' . $labType);
$plain = (int) $this->readSetting('DIAG_COMPILE_TPL_PLAIN_' . $labType);
if ($this->db->tableExists('diagnosis_print_templates')) {
if ($letterhead <= 0) {
$defaultRow = $this->db->table('diagnosis_print_templates')
->select('id')
->where('modality', $labType)
->where('status', 1)
->orderBy('is_default', 'DESC')
->orderBy('id', 'ASC')
->get(1)
->getRowArray();
$letterhead = (int) ($defaultRow['id'] ?? 0);
}
if ($plain <= 0) {
$plainBuilder = $this->db->table('diagnosis_print_templates')
->select('id')
->where('modality', $labType)
->where('status', 1);
if ($letterhead > 0) {
$plainBuilder->where('id !=', $letterhead);
}
$plainRow = $plainBuilder
->orderBy('is_default', 'DESC')
->orderBy('id', 'ASC')
->get(1)
->getRowArray();
$plain = (int) ($plainRow['id'] ?? 0);
if ($plain <= 0) {
$plain = $letterhead;
}
}
}
return [
'letterhead' => $letterhead,
'plain' => $plain,
];
}
public function imagingUploadGallery($invoiceId, $labType, $labReqId = 0)
{
$invoiceId = (int) $invoiceId;

View File

@ -2256,6 +2256,24 @@ HTML;
$selectedTemplateId = (int) ($this->request->getGet('template_id') ?? 0);
$isNewTemplate = (int) ($this->request->getGet('new') ?? 0) === 1;
$compileLetterheadTemplateId = (int) $this->readHospitalSettingValue('DIAG_COMPILE_TPL_LETTERHEAD_' . $modality);
$compilePlainTemplateId = (int) $this->readHospitalSettingValue('DIAG_COMPILE_TPL_PLAIN_' . $modality);
$currentTemplateType = strtolower(trim((string) ($this->request->getGet('template_type') ?? 'letterhead')));
if (! in_array($currentTemplateType, ['letterhead', 'plain'], true)) {
$currentTemplateType = 'letterhead';
}
$mappedTemplateIdForType = $currentTemplateType === 'plain'
? $compilePlainTemplateId
: $compileLetterheadTemplateId;
if ($selectedTemplateId <= 0) {
if ($mappedTemplateIdForType > 0) {
$selectedTemplateId = $mappedTemplateIdForType;
} else {
$isNewTemplate = true;
}
}
if (strtolower($this->request->getMethod()) === 'post') {
$modality = (int) ($this->request->getPost('modality') ?? $modality);
@ -2263,8 +2281,28 @@ HTML;
$modality = 3;
}
$currentTemplateType = strtolower(trim((string) ($this->request->getPost('template_type') ?? $currentTemplateType)));
if (! in_array($currentTemplateType, ['letterhead', 'plain'], true)) {
$currentTemplateType = 'letterhead';
}
$compileLetterheadTemplateId = (int) ($this->request->getPost('compile_letterhead_template_id') ?? 0);
$compilePlainTemplateId = (int) ($this->request->getPost('compile_plain_template_id') ?? 0);
$selectedTemplateId = (int) ($this->request->getPost('template_id') ?? 0);
$mappedTemplateIdForType = $currentTemplateType === 'plain'
? $compilePlainTemplateId
: $compileLetterheadTemplateId;
if ($mappedTemplateIdForType > 0) {
$selectedTemplateId = $mappedTemplateIdForType;
} elseif ($currentTemplateType === 'plain' && $compileLetterheadTemplateId > 0 && $selectedTemplateId === $compileLetterheadTemplateId) {
$selectedTemplateId = 0;
} elseif ($currentTemplateType === 'letterhead' && $compilePlainTemplateId > 0 && $selectedTemplateId === $compilePlainTemplateId) {
$selectedTemplateId = 0;
}
if (! $columnsReady) {
$notice = 'Print settings columns are missing. Please run migration first.';
$noticeType = 'danger';
@ -2278,10 +2316,7 @@ HTML;
->getRowArray() ?? [];
}
$templateName = trim((string) ($this->request->getPost('template_name') ?? ''));
if ($templateName === '') {
$templateName = 'Template ' . date('d-m-Y H:i');
}
$templateName = $currentTemplateType === 'plain' ? 'Plain Paper' : 'Letter Head';
$removeBackground = (int) ($this->request->getPost('remove_background') ?? 0) === 1;
$removeWatermarkImage = (int) ($this->request->getPost('remove_watermark_image') ?? 0) === 1;
@ -2384,7 +2419,7 @@ 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,
'is_default' => ! empty($existing) ? (int) ($existing['is_default'] ?? 0) : 0,
'status' => 1,
];
@ -2393,11 +2428,18 @@ HTML;
}
if ($noticeType !== 'danger') {
if ($data['is_default'] === 1) {
$templateTable
if (empty($existing)) {
$hasDefaultTemplate = (bool) ($templateTable
->select('id')
->where('modality', $modality)
->set('is_default', 0)
->update();
->where('status', 1)
->where('is_default', 1)
->get(1)
->getRowArray());
if (! $hasDefaultTemplate && $currentTemplateType === 'letterhead') {
$data['is_default'] = 1;
}
}
if (! empty($existing) && $selectedTemplateId > 0) {
@ -2412,6 +2454,27 @@ HTML;
$notice = (($modalityList[$modality] ?? 'Diagnosis') . ' print template saved.');
$noticeType = 'success';
if ($currentTemplateType === 'plain') {
$compilePlainTemplateId = $selectedTemplateId;
} else {
$compileLetterheadTemplateId = $selectedTemplateId;
}
$letterheadSettingKey = 'DIAG_COMPILE_TPL_LETTERHEAD_' . $modality;
$plainSettingKey = 'DIAG_COMPILE_TPL_PLAIN_' . $modality;
if ($compileLetterheadTemplateId > 0) {
$this->upsertHospitalSettingValue($letterheadSettingKey, (string) $compileLetterheadTemplateId);
} else {
$this->deleteHospitalSettingValue($letterheadSettingKey);
}
if ($compilePlainTemplateId > 0) {
$this->upsertHospitalSettingValue($plainSettingKey, (string) $compilePlainTemplateId);
} else {
$this->deleteHospitalSettingValue($plainSettingKey);
}
}
}
@ -2455,6 +2518,9 @@ HTML;
'row' => $row,
'templates' => $templates,
'selected_template_id' => $selectedTemplateId,
'compile_letterhead_template_id' => $compileLetterheadTemplateId,
'compile_plain_template_id' => $compilePlainTemplateId,
'current_template_type' => $currentTemplateType,
'notice' => $notice,
'notice_type' => $noticeType,
'columns_ready' => $columnsReady,
@ -2464,6 +2530,49 @@ HTML;
]);
}
private function readHospitalSettingValue(string $name): string
{
if ($name === '' || ! $this->db->tableExists('hospital_setting')) {
return '';
}
$row = $this->db->table('hospital_setting')
->select('s_value')
->where('s_name', $name)
->get(1)
->getRowArray();
return trim((string) ($row['s_value'] ?? ''));
}
private function upsertHospitalSettingValue(string $name, string $value): bool
{
if ($name === '' || ! $this->db->tableExists('hospital_setting')) {
return false;
}
$table = $this->db->table('hospital_setting');
$existing = $table->select('id')->where('s_name', $name)->get(1)->getRowArray();
if ($existing) {
return (bool) $table->where('id', (int) ($existing['id'] ?? 0))->update(['s_value' => $value]);
}
return (bool) $table->insert([
's_name' => $name,
's_value' => $value,
]);
}
private function deleteHospitalSettingValue(string $name): bool
{
if ($name === '' || ! $this->db->tableExists('hospital_setting')) {
return false;
}
return (bool) $this->db->table('hospital_setting')->where('s_name', $name)->delete();
}
public function document_print_settings()
{
if ($resp = $this->requireAnyPermission([

View File

@ -7,9 +7,22 @@ $columnsReady = (bool) ($columns_ready ?? false);
$modalityList = $modality_list ?? [];
$templates = $templates ?? [];
$selectedTemplateId = (int) ($selected_template_id ?? 0);
$compileLetterheadTemplateId = (int) ($compile_letterhead_template_id ?? 0);
$compilePlainTemplateId = (int) ($compile_plain_template_id ?? 0);
$currentTemplateType = (string) ($current_template_type ?? 'letterhead');
$hasSignatureImageColumn = (bool) ($has_signature_image_column ?? false);
$modalityLabel = (string) ($modalityList[$modality] ?? 'Diagnosis');
$panelTitle = $modalityLabel . ' Print Template';
$templateNameById = [];
foreach ($templates as $tpl) {
$tplId = (int) ($tpl['id'] ?? 0);
if ($tplId > 0) {
$templateNameById[$tplId] = (string) ($tpl['template_name'] ?? ('Template ' . $tplId));
}
}
$compileLetterheadTemplateName = (string) ($templateNameById[$compileLetterheadTemplateId] ?? ($compileLetterheadTemplateId > 0 ? 'Template ' . $compileLetterheadTemplateId : 'Use modality default template'));
$compilePlainTemplateName = (string) ($templateNameById[$compilePlainTemplateId] ?? ($compilePlainTemplateId > 0 ? 'Template ' . $compilePlainTemplateId : 'Use modality default template'));
?>
<section class="content">
@ -35,6 +48,10 @@ $panelTitle = $modalityLabel . ' Print Template';
<form method="post" action="<?= base_url('setting/template/diagnosis_print_settings/' . $modality) ?>" enctype="multipart/form-data" id="diag_print_setting_form">
<?= csrf_field() ?>
<input type="hidden" name="template_id" value="<?= (int) ($row['id'] ?? $selectedTemplateId) ?>">
<input type="hidden" name="template_type" id="template_type_input" value="<?= esc($currentTemplateType) ?>">
<input type="hidden" name="compile_letterhead_template_id" id="compile_letterhead_template_id" value="<?= (int) $compileLetterheadTemplateId ?>">
<input type="hidden" name="compile_plain_template_id" id="compile_plain_template_id" value="<?= (int) $compilePlainTemplateId ?>">
<input type="hidden" name="template_name" id="template_name_input" value="<?= esc($currentTemplateType === 'plain' ? 'Plain Paper' : 'Letter Head') ?>">
<div class="row g-2">
<div class="col-md-3">
<label class="form-label small">Modality</label>
@ -44,30 +61,19 @@ $panelTitle = $modalityLabel . ' Print Template';
<?php endforeach; ?>
</select>
</div>
<div class="col-md-5">
<label class="form-label small">Template Name</label>
<input type="text" class="form-control form-control-sm" name="template_name" value="<?= esc((string) ($row['template_name'] ?? '')) ?>" placeholder="e.g. Standard <?= esc($modalityLabel) ?> Template" required>
</div>
<div class="col-md-4 d-flex align-items-end">
<div class="form-check mb-1">
<input class="form-check-input" type="checkbox" name="is_default" id="is_default" value="1" <?= ((int) ($row['is_default'] ?? 0) === 1) ? 'checked' : '' ?>>
<label class="form-check-label" for="is_default">Set as default template for this modality</label>
</div>
</div>
<div class="col-md-9"></div>
</div>
<div class="mt-2 mb-2">
<label class="form-label small mb-1">Template List</label>
<div class="d-flex flex-wrap gap-2">
<?php foreach ($templates as $tpl): ?>
<?php
$tplId = (int) ($tpl['id'] ?? 0);
$tplName = (string) ($tpl['template_name'] ?? ('Template ' . $tplId));
$activeClass = ($tplId === (int) ($row['id'] ?? $selectedTemplateId)) ? 'btn-primary' : 'btn-outline-primary';
?>
<a class="btn btn-sm <?= $activeClass ?>" href="javascript:load_form_div('<?= base_url('setting/template/diagnosis_print_settings/' . $modality . '?template_id=' . $tplId) ?>','maindiv','<?= esc($panelTitle) ?>');"><?= esc($tplName) ?></a>
<?php endforeach; ?>
<a class="btn btn-sm btn-outline-success" href="javascript:load_form_div('<?= base_url('setting/template/diagnosis_print_settings/' . $modality . '?new=1') ?>','maindiv','<?= esc($panelTitle) ?>');">+ New Template</a>
<div class="row g-2 mt-2 mb-2">
<div class="col-md-6">
<label class="form-label small mb-1">Template Type</label>
<select class="form-select form-select-sm" id="template_type_selector" onchange="loadDiagnosisTemplateType(this.value)">
<option value="letterhead" <?= $currentTemplateType === 'letterhead' ? 'selected' : '' ?>>Letter Head</option>
<option value="plain" <?= $currentTemplateType === 'plain' ? 'selected' : '' ?>>Plain Paper</option>
</select>
</div>
<div class="col-md-6 d-flex align-items-end">
<button type="button" class="btn btn-sm btn-outline-success" onclick="loadDiagnosisTemplateType(document.getElementById('template_type_selector') ? document.getElementById('template_type_selector').value : 'letterhead')">Load Template</button>
</div>
</div>
@ -136,7 +142,7 @@ $panelTitle = $modalityLabel . ' Print Template';
</div>
<div class="col-md-2">
<label class="form-label small">Watermark Alpha</label>
<input type="number" $11" min="0.01" max="1" class="form-control form-control-sm" name="watermark_alpha" value="<?= esc((string) ($row['watermark_alpha'] ?? '0.12')) ?>">
<input type="number" step="0.01" min="0.01" max="1" class="form-control form-control-sm" name="watermark_alpha" value="<?= esc((string) ($row['watermark_alpha'] ?? '0.12')) ?>">
</div>
<div class="col-md-6">
<label class="form-label small">Watermark Text</label>
@ -317,10 +323,66 @@ $panelTitle = $modalityLabel . ' Print Template';
}
};
window.loadDiagnosisTemplateType = function (value) {
var type = String(value || 'letterhead');
var templateId = 0;
var letterInput = document.getElementById('compile_letterhead_template_id');
var plainInput = document.getElementById('compile_plain_template_id');
var typeInput = document.getElementById('template_type_input');
var templateNameInput = document.getElementById('template_name_input');
if (typeInput) {
typeInput.value = type;
}
if (templateNameInput) {
templateNameInput.value = type === 'plain' ? 'Plain Paper' : 'Letter Head';
}
if (type === 'plain') {
templateId = plainInput ? parseInt(plainInput.value || '0', 10) : 0;
} else {
templateId = letterInput ? parseInt(letterInput.value || '0', 10) : 0;
}
var url = '<?= base_url('setting/template/diagnosis_print_settings/' . $modality) ?>';
var query = ['template_type=' + encodeURIComponent(type)];
if (templateId > 0) {
query.push('template_id=' + encodeURIComponent(String(templateId)));
} else {
query.push('new=1');
}
url += '?' + query.join('&');
if (typeof load_form_div === 'function') {
load_form_div(url, 'maindiv', '<?= esc($panelTitle) ?>');
} else {
window.location.href = url;
}
};
if (form) {
form.addEventListener('submit', function (event) {
event.preventDefault();
var typeInput = document.getElementById('template_type_input');
var currentType = typeInput ? String(typeInput.value || 'letterhead') : 'letterhead';
var templateNameInput = document.getElementById('template_name_input');
var templateIdInput = form.querySelector('input[name="template_id"]');
var letterInput = document.getElementById('compile_letterhead_template_id');
var plainInput = document.getElementById('compile_plain_template_id');
if (templateNameInput) {
templateNameInput.value = currentType === 'plain' ? 'Plain Paper' : 'Letter Head';
}
if (templateIdInput) {
if (currentType === 'plain') {
templateIdInput.value = plainInput ? String(parseInt(plainInput.value || '0', 10) || 0) : '0';
} else {
templateIdInput.value = letterInput ? String(parseInt(letterInput.value || '0', 10) || 0) : '0';
}
}
var submitBtn = form.querySelector('button[type="submit"]');
if (submitBtn) {
submitBtn.disabled = true;
@ -341,6 +403,32 @@ $panelTitle = $modalityLabel . ' Print Template';
var message = (result && result.notice) ? result.notice : 'Unable to save settings.';
showNotice(status, message);
if (result && result.selected_template_id) {
var tplInput = form.querySelector('input[name="template_id"]');
if (tplInput) {
tplInput.value = String(result.selected_template_id);
}
var typeInput = document.getElementById('template_type_input');
var currentType = typeInput ? String(typeInput.value || 'letterhead') : 'letterhead';
if (currentType === 'plain') {
var plainInput = document.getElementById('compile_plain_template_id');
if (plainInput) {
plainInput.value = String(result.selected_template_id);
}
} else {
var letterInput = document.getElementById('compile_letterhead_template_id');
if (letterInput) {
letterInput.value = String(result.selected_template_id);
}
}
var templateTypeSelector = document.getElementById('template_type_selector');
if (templateTypeSelector) {
templateTypeSelector.value = currentType;
}
}
if (result && result.csrfName && result.csrfHash) {
var csrfInput = form.querySelector('input[name="' + result.csrfName + '"]');
if (csrfInput) {
@ -358,5 +446,16 @@ $panelTitle = $modalityLabel . ' Print Template';
});
});
}
var templateTypeSelector = document.getElementById('template_type_selector');
if (templateTypeSelector) {
templateTypeSelector.addEventListener('change', function () {
var typeInput = document.getElementById('template_type_input');
if (typeInput) {
typeInput.value = this.value;
}
window.loadDiagnosisTemplateType(this.value);
});
}
})();
</script>

View File

@ -202,7 +202,7 @@ $hasTemplateAccess = $canPathology || $canUltrasound || $canXray || $canCt || $c
<?php if ($canOpdPrintTemplate) { ?>
<div class="col-6 col-md-2 col-lg-2">
<a class="admin-tile" href="javascript:load_form('<?= base_url('Opd/print_template_builder') ?>','maindiv','OPD Print Template');">
<a class="admin-tile" href="javascript:load_form_div('<?= base_url('Opd/print_template_builder') ?>','maindiv','OPD Print Template');">
<span class="tile-icon"><i class="bi bi-printer"></i></span>
<span>OPD Print Template</span>
</a>
@ -211,7 +211,7 @@ $hasTemplateAccess = $canPathology || $canUltrasound || $canXray || $canCt || $c
<?php if ($canDischarge) { ?>
<div class="col-6 col-md-2 col-lg-2">
<a class="admin-tile" href="javascript:load_form('<?= base_url('setting/template/discharge_templates') ?>','maindiv','IPD Discharge Template');">
<a class="admin-tile" href="javascript:load_form_div('<?= base_url('setting/template/discharge_templates') ?>','maindiv','IPD Discharge Template');">
<span class="tile-icon"><i class="bi bi-file-earmark-medical"></i></span>
<span>IPD Discharge Template</span>
</a>
@ -220,7 +220,7 @@ $hasTemplateAccess = $canPathology || $canUltrasound || $canXray || $canCt || $c
<?php if ($canIpdDocumentMaster) { ?>
<div class="col-6 col-md-2 col-lg-2">
<a class="admin-tile" href="javascript:load_form('<?= base_url('setting/template/ipd_document_templates') ?>','maindiv','IPD Document Master');">
<a class="admin-tile" href="javascript:load_form_div('<?= base_url('setting/template/ipd_document_templates') ?>','maindiv','IPD Document Master');">
<span class="tile-icon"><i class="bi bi-file-earmark-richtext"></i></span>
<span>IPD Document Master</span>
</a>
@ -230,7 +230,7 @@ $hasTemplateAccess = $canPathology || $canUltrasound || $canXray || $canCt || $c
<?php if ($canDiagnosisPrintTemplate) { ?>
<?php if ($canPathologyPrintTemplate) { ?>
<div class="col-6 col-md-2 col-lg-2">
<a class="admin-tile" href="javascript:load_form('<?= base_url('setting/template/diagnosis_print_settings/5') ?>','maindiv','Pathology Print Template');">
<a class="admin-tile" href="javascript:load_form_div('<?= base_url('setting/template/diagnosis_print_settings/5') ?>','maindiv','Pathology Print Template');">
<span class="tile-icon"><i class="bi bi-printer"></i></span>
<span>Pathology Print Template</span>
</a>
@ -238,7 +238,7 @@ $hasTemplateAccess = $canPathology || $canUltrasound || $canXray || $canCt || $c
<?php } ?>
<div class="col-6 col-md-2 col-lg-2">
<a class="admin-tile" href="javascript:load_form('<?= base_url('setting/template/diagnosis_print_settings/3') ?>','maindiv','Diagnosis Print Template');">
<a class="admin-tile" href="javascript:load_form_div('<?= base_url('setting/template/diagnosis_print_settings/3') ?>','maindiv','Diagnosis Print Template');">
<span class="tile-icon"><i class="bi bi-printer-fill"></i></span>
<span>Diagnosis Print Template</span>
</a>
@ -247,7 +247,7 @@ $hasTemplateAccess = $canPathology || $canUltrasound || $canXray || $canCt || $c
<?php if ($canDoctorDocumentPrintTemplate) { ?>
<div class="col-6 col-md-2 col-lg-2">
<a class="admin-tile" href="javascript:load_form('<?= base_url('setting/template/document_print_settings') ?>','maindiv','Document Print Template');">
<a class="admin-tile" href="javascript:load_form_div('<?= base_url('setting/template/document_print_settings') ?>','maindiv','Document Print Template');">
<span class="tile-icon"><i class="bi bi-file-earmark-pdf"></i></span>
<span>Document Print Template</span>
</a>

View File

@ -205,6 +205,7 @@ if ($ageLabel === '') {
</button>
</div>
</div>
<div class="mt-2" id="compiledPdfButtons" style="display:none;"></div>
</div>
</div>
<?php endif; ?>
@ -1252,6 +1253,7 @@ function compileReport() {
.then(result => {
if (result.status === 'success') {
alert(result.message || 'Data Compile');
renderCompiledPdfButtons(result);
refreshTestList();
refreshLabDateShow();
} else {
@ -1264,6 +1266,75 @@ function compileReport() {
});
}
function renderCompiledPdfButtons(result) {
const container = document.getElementById('compiledPdfButtons');
if (!container) {
return;
}
const urls = (result && result.pdf_file_urls && typeof result.pdf_file_urls === 'object') ? result.pdf_file_urls : {};
const labels = (result && result.pdf_file_labels && typeof result.pdf_file_labels === 'object') ? result.pdf_file_labels : {};
const letterUrl = urls.letterhead || '';
const plainUrl = urls.plain || '';
const letterLabel = labels.letterhead || 'Default Template';
const plainLabel = labels.plain || 'Default Template';
if (!letterUrl && !plainUrl) {
container.style.display = 'none';
container.innerHTML = '';
return;
}
let html = '<div class="alert alert-success py-2 mb-0"><strong>Compiled PDFs:</strong> ';
if (letterUrl) {
html += '<a class="btn btn-sm btn-outline-primary ms-2" href="' + letterUrl + '" target="_blank" rel="noopener"><i class="bi bi-file-earmark-pdf"></i> Letter Head - ' + escapeHtml(letterLabel) + '</a>';
}
if (plainUrl) {
html += '<a class="btn btn-sm btn-outline-secondary ms-2" href="' + plainUrl + '" target="_blank" rel="noopener"><i class="bi bi-file-earmark-pdf"></i> Plain Paper - ' + escapeHtml(plainLabel) + '</a>';
}
html += '</div>';
container.innerHTML = html;
container.style.display = '';
}
function escapeHtml(value) {
return String(value || '')
.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&#39;');
}
function loadCompiledPdfButtons() {
const invoiceInput = document.getElementById('invoiceId');
if (!invoiceInput) {
return;
}
const invoiceId = Number(invoiceInput.value || 0);
if (!invoiceId || !labType) {
return;
}
fetch(baseUrl + 'diagnosis/compiled-pdf-links/' + invoiceId + '/' + labType, {
method: 'GET',
headers: {
'X-Requested-With': 'XMLHttpRequest'
}
})
.then(response => response.json())
.then(result => {
if (result && result.status === 'success') {
renderCompiledPdfButtons(result);
}
})
.catch(error => {
console.error('loadCompiledPdfButtons error:', error);
});
}
function generateReportByTemplate(templateId) {
const tplId = Number(templateId || 0);
const invoiceId = document.getElementById('invoiceId').value;
@ -1292,4 +1363,5 @@ function showReports() {
console.log('Loading data for invoice:', invoiceId, 'labType:', labType);
refreshTestList();
refreshLabDateShow();
loadCompiledPdfButtons();
</script>